GoogleCloudPlatform / GoogleCloudPlatform/recaptcha-enterprise-react-native

API Design Issue: Simplifying Client Instance Management Across Screens

Open
#93 0 comments 0 reactions 0 assignees View on GitHub
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.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.