googleSignIn method
Authenticate with Google OAuth (sends ID token to backend) This skips email verification since Google already verified the email
Implementation
Future<Map<String, dynamic>> googleSignIn({
required String email,
required String idToken,
String? accessToken,
bool testMode = false,
}) async {
print('🔗 [GOOGLE-SIGNIN] Starting Google sign-in for: $email');
OnairosDebugHelper.log('🔗 [GOOGLE-SIGNIN] Starting Google sign-in');
OnairosDebugHelper.log('🔗 [GOOGLE-SIGNIN] Email: $email, testMode: $testMode');
try {
// Test mode mock response
if (testMode) {
OnairosDebugHelper.log('🚀 Test mode: Mocking Google sign-in success');
await Future.delayed(const Duration(milliseconds: 300));
// Generate mock JWT token
final mockJwtToken = _generateMockJwtToken();
// Store JWT token
await _storage.storeJwtToken(mockJwtToken);
await _storage.storeValue('email_verification_token', mockJwtToken);
await _storage.storeValue('email_verification_email', email);
// Check if this email has been seen before
final previouslySeenEmails = await _storage.retrieveValue('known_emails') ?? '';
final knownEmailList = previouslySeenEmails.split(',').where((e) => e.isNotEmpty).toList();
final isExistingUser = knownEmailList.contains(email);
// If this is a new user, remember their email for next time
if (!isExistingUser) {
knownEmailList.add(email);
await _storage.storeValue('known_emails', knownEmailList.join(','));
}
// Mock connected platforms for existing users
List<String> connectedPlatforms = isExistingUser ? ['Instagram', 'YouTube', 'Reddit'] : [];
OnairosDebugHelper.log('✅ Test mode: Google sign-in mocked (${isExistingUser ? "existing" : "new"} user)');
return {
'success': true,
'result': 'User signed in successfully',
'message': 'Google sign-in successful (test mode)',
'existingUser': isExistingUser,
'connectedPlatforms': connectedPlatforms,
'jwtToken': mockJwtToken,
'token': mockJwtToken,
'testMode': true,
};
}
// Guardrail: if Google Sign-In didn’t return an ID token, the backend cannot verify the user.
if (idToken.trim().isEmpty) {
return {
'success': false,
'message': 'Google sign-in did not return an ID token. Please ensure Google Sign-In is configured with a valid serverClientId and try again.',
};
}
// Live mode:
// Prefer the mobile endpoint (used by Flutter) and fall back to the web-style endpoint if needed.
Map<String, dynamic> response;
try {
print('🌐 [GOOGLE-SIGNIN] Making API call to: login/mobile-google-signin');
OnairosDebugHelper.log('🌐 Making API call to: login/mobile-google-signin');
response = await _apiKeyService.authenticatedPost(
'login/mobile-google-signin',
body: {
'details': {
'email': email,
'token': idToken,
},
},
).timeout(
const Duration(seconds: 30),
onTimeout: () {
OnairosDebugHelper.log('⏱️ Google sign-in request timeout after 30 seconds');
throw TimeoutException('Request timed out after 30 seconds', const Duration(seconds: 30));
},
);
} catch (e) {
// Fallback: web-style endpoint (onairos-npm)
print('⚠️ [GOOGLE-SIGNIN] mobile-google-signin failed, falling back to: google/google ($e)');
OnairosDebugHelper.log('⚠️ mobile-google-signin failed, falling back to: google/google');
response = await _apiKeyService.authenticatedPost(
'google/google',
body: {'credential': idToken},
).timeout(
const Duration(seconds: 30),
onTimeout: () {
OnairosDebugHelper.log('⏱️ Google sign-in request timeout after 30 seconds');
throw TimeoutException('Request timed out after 30 seconds', const Duration(seconds: 30));
},
);
}
print('📥 [GOOGLE-SIGNIN] Response: $response');
OnairosDebugHelper.log('📥 Google sign-in response: $response');
// Normalize backend response shape:
// - /google/google returns: { status: 200, body: { token, username, message, isNewUser } }
// - /login/mobile-google-signin returns: { result, token, username, existingUser, connectedPlatforms }
final dynamic inner = (response['body'] is Map) ? response['body'] : response;
final Map<String, dynamic> data =
(inner is Map<String, dynamic>) ? inner : Map<String, dynamic>.from(inner as Map);
final String? token = data['token'] as String?;
final bool success =
token != null && token.isNotEmpty && (response['status'] == 200 || data['success'] == true || data['result'] == 'User signed in successfully');
if (success) {
final jwtToken = token;
if (jwtToken != null) {
// Store JWT token
await _storage.storeJwtToken(jwtToken);
await _storage.storeValue('email_verification_token', jwtToken);
await _storage.storeValue('email_verification_email', email);
final existingUser =
(data['existingUser'] == true) || (data['isNewUser'] == false);
await _storage.storeValue('user_type', existingUser ? 'existing' : 'new');
// Save to native iOS Keychain
try {
const platform = MethodChannel('com.onairos.oboe/connectors');
await platform.invokeMethod<bool>('setJWTToken', {'token': jwtToken});
print('✅ [GOOGLE-SIGNIN] JWT token saved to native iOS Keychain');
} catch (e) {
print('⚠️ [GOOGLE-SIGNIN] Could not save JWT to native iOS: $e');
}
}
OnairosDebugHelper.log('✅ Google sign-in successful');
return {
'success': true,
'result': data['result'] ?? response['result'] ?? 'User signed in successfully',
'existingUser': data['existingUser'] ?? (data['isNewUser'] == false),
'connectedPlatforms': data['connectedPlatforms'] ?? response['connectedPlatforms'] ?? [],
'jwtToken': jwtToken,
'token': jwtToken,
'username': data['username'] ?? response['username'],
};
} else {
final message = data['message'] ?? response['message'] ?? 'Google sign-in failed';
OnairosDebugHelper.log('⚠️ Google sign-in failed: $message');
return {
'success': false,
'message': message,
};
}
} catch (e, stackTrace) {
print('❌ [GOOGLE-SIGNIN] Error: $e');
print('❌ [GOOGLE-SIGNIN] Stack: $stackTrace');
OnairosDebugHelper.log('❌ Google sign-in error: $e');
// Prefer the backend-provided error message when available (helps debugging clientId/audience issues).
String errorMessage = 'Google sign-in failed. Please try again.';
final raw = e.toString();
final apiErrMatch = RegExp(r'API Error \\(\\d+\\):\\s*(.*)$').firstMatch(raw);
if (apiErrMatch != null && (apiErrMatch.group(1)?.trim().isNotEmpty ?? false)) {
errorMessage = apiErrMatch.group(1)!.trim();
}
if (e.toString().contains('404')) {
errorMessage = 'Google sign-in endpoint not found. Please check backend configuration.';
} else if (e.toString().contains('500')) {
errorMessage = 'Backend server error. Please try again later.';
} else if (e.toString().contains('timeout')) {
errorMessage = 'Request timed out. Please check your internet connection.';
}
return {
'success': false,
'message': errorMessage,
'error': e.toString(),
};
}
}