GoogleCloudPlatform / GoogleCloudPlatform/recaptcha-enterprise-react-native
API Design Issue: Simplifying Client Instance Management Across Screens
- Dominant language
- TypeScript
- Stars
- 64
- Forks
- 7
- Avg merge
- 14h 15m
- Merged PRs (30d)
- 3
Description
## Context
I'm currently integrating reCAPTCHA Enterprise into a React Native app and noticed that the recommended API (`Recaptcha.fetchClient`) requires manual management of the `RecaptchaClient` instance across different screens. I wanted to open a discussion about potential improvements to make this easier for developers.
### Current API (Recommended)
```typescript
import { Recaptcha, RecaptchaAction, RecaptchaClient } from '@google-cloud/recaptcha-enterprise-react-native';
// Must initialize once
const client = await Recaptcha.fetchClient(siteKey);
// Now need to pass this client to other screens...
// How do we share this across the app?
```
### The Challenge
The documentation states:
> You must initialize the reCAPTCHA client only once during the lifetime of your app.
However, the API returns a client instance that needs to be shared across multiple screens (e.g., Login, SignUp, Password Reset). This requires developers to implement additional patterns to manage the instance:
#### Solution 1: Context API (Boilerplate Heavy)
```typescript
// RecaptchaContext.tsx
const RecaptchaContext = createContext(null);
export const RecaptchaProvider = ({ children }) => {
const [client, setClient] = useState(null);
useEffect(() => {
Recaptcha.fetchClient(siteKey).then(setClient);
}, []);
return (
{children}
);
};
export const useRecaptcha = () => useContext(RecaptchaContext);
// App.tsx
// LoginScreen.tsx
const client = useRecaptcha();
const token = await client?.execute(RecaptchaAction.LOGIN());
```
#### Solution 2: Singleton Pattern
```typescript
// recaptchaService.ts
class RecaptchaService {
private client: RecaptchaClient | null = null;
async initialize(siteKey: string) {
if (!this.client) {
this.client = await Recaptcha.fetchClient(siteKey);
}
return this.client;
}
getClient() {
if (!this.client) throw new Error('Not initialized');
return this.client;
}
}
export const recaptchaService = new RecaptchaService();
// Usage
await recaptchaService.initialize(siteKey);
const token = await recaptchaService.getClient().execute(RecaptchaAction.LOGIN());
```
Both solutions work but require additional setup code.
## Observation: The Deprecated API Pattern
I noticed the deprecated API (`initClient` + `execute`) had a simpler usage pattern:
```typescript
import { initClient, execute, RecaptchaAction } from '@google-cloud/recaptcha-enterprise-react-native';
// Initialize once
await initClient(siteKey, 10000);
// Use anywhere in the app - no instance management needed!
const token = await execute(RecaptchaAction.LOGIN(), 10000);
```
This pattern works well because the native layer already manages the client internally. Looking at the implementation:
**Android (RecaptchaEnterpriseReactNativeModule.kt:37):**
```kotlin
private lateinit var recaptchaClient: RecaptchaClient // Stored in module
```
**iOS (RecaptchaEnterpriseReactNative.swift:19):**
```swift
var recaptchaClient: RecaptchaClient? // Stored in module
```
The native modules already handle instance management internally.
## Question
**What was the reasoning behind deprecating `initClient/execute` in favor of the instance-based approach?**
## Reference: Patterns from Similar Libraries
### React Native Firebase
```typescript
import { getAuth, signInWithEmailAndPassword } from '@react-native-firebase/auth';
// Functional API - no manual instance management
await signInWithEmailAndPassword(getAuth(), email, password);
```
### Firebase JS SDK
```typescript
import { initializeApp } from 'firebase/app';
import { getAuth, signInWithEmailAndPassword } from 'firebase/auth';
// Initialize once
const app = initializeApp(config);
// Functional API - pass auth instance to functions
await signInWithEmailAndPassword(getAuth(app), email, password);
```
### AWS Amplify
```typescript
import { Amplify, Auth } from 'aws-amplify';
// Configure once
Amplify.configure(awsconfig);
// Use anywhere
await Auth.signIn(username, password);
```
These libraries provide different approaches to instance management that might be worth considering.
## Potential Approaches
### Approach 1: Static Methods (Similar to Amplify)
```typescript
// Configure once
await Recaptcha.configure({ siteKey: 'YOUR_SITE_KEY' });
// Use anywhere - no instance needed
const token = await Recaptcha.execute(RecaptchaAction.LOGIN());
// Check if configured
if (Recaptcha.isConfigured()) {
// Ready to use
}
```
### Approach 2: Improve and Re-introduce Functional API
```typescript
export { initClient as configure };
export { execute };
// Usage
await configure(siteKey);
const token = await execute(RecaptchaAction.LOGIN());
```
## Potential Benefits
Any of these approaches could provide:
- Reduced boilerplate for common use cases
- Easier usage across multiple screens
- Simpler onboarding experience
I'm happy to contribute an implementation if there's interest in this direction.
---
**Environment:**
- Package version: 18.9.0-beta01
- React Native version: 0.83.0
- Platform: iOS & Android
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.