# Native Fetcher Configuration Guide

**Author**: Matthew Raymer  
**Date**: 2025-01-28  
**Status**: 🎯 **REFERENCE** - Complete guide for configuring native fetchers

## Overview

The `configureNativeFetcher()` method provides a **cross-platform** mechanism for passing API credentials from TypeScript/JavaScript code to native fetcher implementations. This guide explains how to use this feature, why it exists, and provides complete examples.

## Table of Contents

- [Why `configureNativeFetcher()`?](#why-configurenativefetcher)
- [Architecture](#architecture)
- [TypeScript Interface](#typescript-interface)
- [Native Implementation](#native-implementation)
- [Complete Example](#complete-example)
- [Error Handling](#error-handling)
- [Thread Safety](#thread-safety)
- [Best Practices](#best-practices)
- [Troubleshooting](#troubleshooting)

---

## Why `configureNativeFetcher()`?

### The Problem

Native fetchers (implementing `NativeNotificationContentFetcher`) are called from background workers (WorkManager on Android, BGTaskScheduler on iOS). These background workers cannot access:
- JavaScript/TypeScript variables
- Capacitor bridge (unreliable in background)
- React/Vue component state
- WebView storage

### The Solution

`configureNativeFetcher()` provides a **direct injection** mechanism:
- ✅ **Cross-platform**: Same TypeScript interface on Android, iOS, and web
- ✅ **Simple**: Only 3 required parameters (apiBaseUrl, activeDid, jwtSecret)
- ✅ **Type-safe**: TypeScript types ensure correct usage
- ✅ **No storage dependency**: Doesn't require SharedPreferences, UserDefaults, etc.

### Alternatives (and why we don't use them)

| Approach | Why Not |
|----------|---------|
| Hardcode in native code | ❌ Can't switch environments, credentials in code |
| SharedPreferences/UserDefaults | ❌ Platform-specific, complex sync |
| JS bridge at fetch time | ❌ Unreliable in background workers |
| Capacitor preferences plugin | ❌ Overhead, async issues in background |

---

## Architecture

```
┌─────────────────────────────────────────────────────────┐
│ TypeScript/JavaScript (Host App)                        │
│                                                          │
│  await DailyNotification.configureNativeFetcher({      │
│    apiBaseUrl: 'http://10.0.2.2:3000',                 │
│    activeDid: 'did:ethr:0x...',                        │
│    jwtSecret: 'secret'                                  │
│  });                                                    │
└─────────────────────┬───────────────────────────────────┘
                      │ Capacitor Bridge
                      ▼
┌─────────────────────────────────────────────────────────┐
│ DailyNotificationPlugin (Android/iOS)                   │
│                                                          │
│  @PluginMethod                                          │
│  configureNativeFetcher(PluginCall call) {              │
│    fetcher.configure(apiBaseUrl, activeDid, jwtSecret); │
│  }                                                      │
└───────────────────────┬─────────────────────────────────────────┘
                      │ Direct method call
                      ▼
┌─────────────────────────────────────────────────────────┐
│ NativeNotificationContentFetcher (Host App Native Code) │
│                                                          │
│  @Override                                              │
│  void configure(String apiBaseUrl,                      │
│                String activeDid,                        │
│                String jwtSecret) {                      │
│    this.apiBaseUrl = apiBaseUrl;                        │
│    this.activeDid = activeDid;                          │
│    this.jwtSecret = jwtSecret;                          │
│  }                                                      │
└─────────────────────────────────────────────────────────┘
```

---

## TypeScript Interface

### Method Signature

```typescript
interface DailyNotificationPlugin {
  configureNativeFetcher(options: {
    apiBaseUrl: string;   // Base URL for API server
    activeDid: string;    // Active DID for authentication
    jwtSecret: string;    // JWT secret for signing tokens
  }): Promise謹<void>;
}
```

### Parameters

| Parameter | Type | Required | Description | Example |
|-----------|------|----------|-------------|---------|
| `apiBaseUrl` | `string` | ✅ Yes | Base URL for API server. For Android emulator, use `10.0.2.2` to access host machine's `localhost`. | `"http://10.0.2.2:3000"` (Android emulator)<br>`"http://localhost:3000"` (iOS simulator)<br>`"https://api.timesafari.com"` (production) |
| `activeDid` | `string` | ✅ Yes | Active DID (Decentralized Identifier) for authentication. Used as JWT issuer/subject. | `"did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F"` |
| `jwtSecret` | `string` | ✅ Yes | JWT secret key for signing authentication tokens. **Keep secure!** | `"test-jwt-secret-for-development"` |

### Return Value

- **Success**: `Promise<void>` resolves
- **Failure**: `Promise<void>` rejects with error message

### Example Usage

```typescript
import { DailyNotification } from '@capacitor-community/daily-notification';
import { TEST_USER_ZERO_CONFIG } from './config/test-user-zero';

async function initializeNotificationPlugin() {
  try {
    // Configure native fetcher with User Zero credentials
    await DailyNotification.configureNativeFetcher({
      apiBaseUrl: TEST_USER_ZERO_CONFIG.getApiServerUrl(),
      activeDid: TEST_USER_ZERO_CONFIG.identity.did,
      jwtSecret: 'test-jwt-secret-for-user-zero-development-only'
    });
    
    console.log('✅ Native fetcher configured successfully');
  } catch (error) {
    console.error('❌ Failed to configure native fetcher:', error);
    throw error;
  }
}
```

---

## Native Implementation

### Java/Kotlin Interface

The `NativeNotificationContentFetcher` interface includes an optional `configure()` method:

```java
public interface NativeNotificationContentFetcher {
    CompletableFuture<List<NotificationContent>> fetchContent(FetchContext context);
    
    /**
     * Optional: Configure the native fetcher with API credentials
     */
    default void configure(String apiBaseUrl, String activeDid, String jwtSecret) {
        // Default no-op - fetchers that need config can override
    }
}
```

### Implementation Pattern

```java
public class MyNativeFetcher implements NativeNotificationContentFetcher {
    private static final String TAG = "MyNativeFetcher";
    
    // Use volatile for thread-safe access from background workers
    private volatile String apiBaseUrl;
    private volatile String activeDid;
    private volatile String jwtSecret;
    
    @Override
    public void configure(String apiBaseUrl, String activeDid, String jwtSecret) {
        this.apiBaseUrl = apiBaseUrl;
        this.activeDid = activeDid;
        this.jwtSecret = jwtSecret;
        
        Log.i(TAG, "Fetcher configured - API: " + apiBaseUrl + 
              ", DID: " + activeDid.substring(0, Math.min(30, activeDid.length())) + "...");
    }
    
    @Override
    public CompletableFuture<List<NotificationContent>> fetchContent(FetchContext context) {
        return CompletableFuture.supplyAsync(() -> {
            // Check if configured
            if (apiBaseUrl == null || activeDid == null || jwtSecret == null) {
                Log.e(TAG, "Not configured! Call configureNativeFetcher() from TypeScript first.");
                return Collections.emptyList();
            }
            
            // Use configured values to make API calls
            // ...
        });
    }
}
```

### When to Override `configure()`

**Override if:**
- ✅ Your fetcher needs API credentials from TypeScript
- ✅ Configuration should come from app config (not hardcoded)
- ✅ You want to avoid platform-specific storage

**Use default no-op if:**
- ✅ Credentials come from platform storage (SharedPreferences, Keychain)
- ✅ You have hardcoded test credentials
- ✅ Configuration is handled internally

---

## Complete Example

### 1. Register Native Fetcher (Android)

**File**: `android/app/src/main/java/com/example/app/MyApplication.java`

```java
package com.example.app;

import android.app.Application;
import org.timesafari.dailynotification.DailyNotificationPlugin;
import org.timesafari.dailynotification.NativeNotificationContentFetcher;

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        
        // Register native fetcher
        NativeNotificationContentFetcher fetcher = new MyNativeFetcher();
        DailyNotificationPlugin.setNativeFetcher(fetcher);
    }
}
```

**AndroidManifest.xml**:
```xml
<application android:name=".MyApplication">
    <!-- ... -->
</application>
```

### 2. Configure from TypeScript

**File**: `src/services/NotificationService.ts`

```typescript
import { DailyNotification } from '@capacitor-community/daily-notification';
import { getApiServerUrl, getActiveDid, getJwtSecret } from './config';

export async function setupDailyNotifications() {
  // Step 1: Configure native fetcher
  await DailyNotification.configureNativeFetcher({
    apiBaseUrl: getApiServerUrl(),
    activeDid: getActiveDid(),
    jwtSecret: getJwtSecret()
  });
  
  // Step 2: Configure plugin policy
  await DailyNotification.setPolicy({
    prefetchWindowMs: 5 * 60 * 1000, // 5 minutes
    retryBackoff: {
      minMs: 2000,
      maxMs: 600000,
      factor: 2,
      jitterPct: 20
    }
  });
  
  // Step 3: Schedule notifications
  // ...
}
```

### 3. Native Fetcher Implementation

**File**: `android/app/src/main/java/com/example/app/MyNativeFetcher.java`

```java
package com.example.app;

import android.util.Log;
import org.timesafari.dailynotification.*;
import java.util.*;

public class MyNativeFetcher implements NativeNotificationContentFetcher {
    private static final String TAG = "MyNativeFetcher";
    private static final String ENDORSER_ENDPOINT = "/api/v2/report/plansLastUpdatedBetween";
    
    // Thread-safe configuration storage
    private volatile String apiBaseUrl;
    private volatile String activeDid;
    private volatile String jwtSecret;
    
    @Override
    public void configure(String apiBaseUrl, String activeDid, String jwtSecret) {
        this.apiBaseUrl = apiBaseUrl;
        this.activeDid = activeDid;
        this.jwtSecret = jwtSecret;
        Log.i(TAG, "Configured with API: " + apiBaseUrl);
    }
    
    @Override
    public CompletableFuture<List<NotificationContent>> fetchContent(FetchContext context) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                // Validate configuration
                if (apiBaseUrl == null || activeDid == null || jwtSecret == null) {
                    Log.e(TAG, "Not configured! Call configureNativeFetcher() first.");
                    return Collections.emptyList();
                }
                
                // Generate JWT token
                String jwt = generateJWT(activeDid, jwtSecret);
                
                // Make API call
                String url = apiBaseUrl + reconstituir ENDORSER_ENDPOINT;
                // ... HTTP request logic ...
                
                // Convert response to NotificationContent
                List<NotificationContent> contents = parseResponse(response);
                return contents;
                
            } catch (Exception e) {
                Log.e(TAG, "Fetch failed", e);
                return Collections.emptyList();
            }
        });
    }
    
    private String generateJWT(String did, String secret) {
        // JWT generation logic
        // ...
    }
}
```

---

## Error Handling

### TypeScript Errors

```typescript
try {
  await DailyNotification.configureNativeFetcher({
    apiBaseUrl: 'http://10.0.2.2:3000',
    activeDid: 'did:ethr:0x...',
    jwtSecret: 'secret'
  });
} catch (error) {
  // Possible errors:
  // - "Missing required parameters: apiBaseUrl, activeDid, and jwtSecret are required"
  // - "No native fetcher registered. Register one in Application.onCreate() before configuring."
  // - "Failed to configure native fetcher: <native exception message>"
  console.error('Configuration failed:', error);
}
```

### Native Fetcher Validation

```java
@Override
public CompletableFuture<List<NotificationContent>> fetchContent(FetchContext context) {
    return CompletableFuture.supplyAsync(() -> {
        // Always check configuration before use
        if (apiBaseUrl == null || activeDid == null || jwtSecret == null) {
            Log.e(TAG, "Not configured! Returning empty list.");
            return Collections.emptyList();
        }
        
        // Proceed with fetch...
    });
}
```

---

## Thread Safety

### Background Worker Context

Native fetchers are called from **background worker threads** (WorkManager, BGTaskScheduler). Configuration may be set from the **main thread** (TypeScript call).

### Volatile Fields

Use `volatile` for configuration fields to ensure:
- ✅ Changes are immediately visible to all threads
- ✅ No caching of stale values
- ✅ Thread-safe access without synchronization

```java
// ✅ Correct: Volatile fields
private volatile String apiBaseUrl;
private volatile String activeDid;
private volatile String jwtSecret;

// ❌ Incorrect: Non-volatile fields (may see stale values)
private String apiBaseUrl;
private String activeDid;
private String jwtSecret;
```

### Reconfiguration

Configuration can be updated at any time:

```typescript
// Update configuration when user switches accounts
await DailyNotification.configureNativeFetcher({
  apiBaseUrl: newApiUrl,
  activeDid: newActiveDid,
  jwtSecret: newJwtSecret
});
```

Subsequent `fetchContent()` calls will use the new configuration immediately.

---

## Best Practices

### 1. Configure Early

Call `configureNativeFetcher()` as soon as credentials are available:

```typescript
// ✅ Good: Configure immediately after app startup
await appInit();
await DailyNotification.configureNativeFetcher(config);

// ❌ Bad: Wait until first fetch
// (background worker might trigger before configuration)
```

### 2. Validate in Native Code

Always validate configuration before use:

```java
@Override
public CompletableFuture<List<NotificationContent>> fetchContent(FetchContext context) {
    if (apiBaseUrl == null || activeDid == null || jwtSecret == null) {
        return CompletableFuture.completedFuture(Collections.emptyList());
    }
    // ...
}
```

### 3. Log Configuration (Sanitized)

Log configuration for debugging, but sanitize sensitive data:

```java
@Override
public void configure(String apiBaseUrl, String activeDid, String jwtSecret) {
    this.apiBaseUrl = apiBaseUrl;
    this.activeDid = activeDid;
    this.jwtSecret = jwtSecret;
    
    Log.i(TAG, "Configured - API: " + apiBaseUrl + 
          ", DID: " + activeDid.substring(0, Math.min(30, activeDid.length())) + "...");
    // Don't log jwtSecret!
}
```

### 4. Handle Reconfiguration

If configuration changes (e.g., user switches accounts), update gracefully:

```java
@Override
public void configure(String apiBaseUrl, String activeDid, String jwtSecret) {
     // Clear any cached state if DID changes
     if (this.activeDid != null && !this.activeDid.equals(activeDid)) {
        Log.i(TAG, "ActiveDid changed - clearing cache");
        clearCache();
    }
    
    this.apiBaseUrl = apiBaseUrl;
    this.activeDid = activeDid;
    this.jwtSecret = jwtSecret;
}
```

### 5. Secure JWT Secrets

In production, consider:
- Using secure storage (Android Keystore, iOS Keychain)
- Deriving secret from device-specific keys
- Using short-lived secrets with rotation

```typescript
// Example: Get secret from secure storage
const jwtSecret = await SecureStorage.get({ key: 'jwt_secret' });
await DailyNotification.configureNativeFetcher({
  apiBaseUrl: apiUrl,
  activeDid: activeDid,
  jwtSecret: jwtSecret.value
});
```

---

## Troubleshooting

### Error: "No native fetcher registered"

**Cause**: Native fetcher not registered in `Application.onCreate()`

**Solution**:
```java
// In MyApplication.java
@Override
public void onCreate() {
    super.onCreate();
    DailyNotificationPlugin.setNativeFetcher(new MyNativeFetcher());
}
```

### Error: "Missing required parameters"

**Cause**: One or more parameters not provided

**Solution**: Ensure all three parameters are provided:
```typescript
await DailyNotification.configureNativeFetcher({
  apiBaseUrl: 'http://10.0.2.2:3000',  // ✅ Required
  activeDid: 'did:ethr:0x...',          // ✅ Required
  jwtSecret: 'secret'                    // ✅ Required
});
```

### Fetcher Not Using Configuration

**Cause**: Native fetcher not checking configuration before use

**Solution**: Validate in `fetchContent()`:
```java
if (apiBaseUrl == null || activeDid == null || jwtSecret == null) {
    Log.e(TAG, "Not configured!");
    return Collections.emptyList();
}
```

### Configuration Lost After App Restart

**Cause**: Configuration is in-memory only (by design)

**Solution**: Reconfigure after app startup:
```typescript
// In app initialization
platform.whenReady(() => {
  setupDailyNotifications(); // Calls configureNativeFetcher()
});
```

---

## Related Documentation

- **[Integration Point Refactor Guide](./INTEGRATION_REFACTOR_QUICK_START.md)** - Overall architecture
- **[API Reference](./API.md)** - Complete plugin API
- **[Native Fetcher Examples](../examples/native-fetcher-android.kt)** - Kotlin implementation examples
- **[Test Implementation](../test-apps/daily-notification-test/android/app/src/main/java/com/timesafari/dailynotification/test/TestNativeFetcher.java)** - Complete reference implementation

---

**Last Updated**: 2025-01-28  
**Version**: 1.0.0

