/** * NativeNotificationContentFetcher.java * * Service Provider Interface (SPI) for native content fetchers. * * This interface is part of the Integration Point Refactor (PR1) that allows * host apps to provide their own content fetching logic without hardcoding * TimeSafari-specific code in the plugin. * * Host apps implement this interface in native code (Kotlin/Java) and register * it with the plugin. The plugin calls this interface from background workers * (WorkManager) to fetch notification content. * * @author Matthew Raymer * @version 1.0.0 */ package com.timesafari.dailynotification; import androidx.annotation.NonNull; import java.util.List; import java.util.concurrent.CompletableFuture; /** * Native content fetcher interface for host app implementations * * This interface enables the plugin to call host app's native code for * fetching notification content. This is the ONLY path used by background * workers, as JavaScript bridges are unreliable in background contexts. * * Implementation Requirements: * - Must be thread-safe (may be called from WorkManager background threads) * - Must complete within reasonable time (plugin enforces timeout) * - Should return empty list on failure rather than throwing exceptions * - Should handle errors gracefully and log for debugging * * Example Implementation: *
* class TimeSafariNativeFetcher implements NativeNotificationContentFetcher {
* private final TimeSafariApi api;
* private final TokenProvider tokenProvider;
*
* @Override
* public CompletableFuture> fetchContent(
* FetchContext context) {
* return CompletableFuture.supplyAsync(() -> {
* try {
* String jwt = tokenProvider.freshToken();
* // Fetch from TimeSafari API
* // Convert to NotificationContent[]
* return notificationContents;
* } catch (Exception e) {
* Log.e("Fetcher", "Fetch failed", e);
* return Collections.emptyList();
* }
* });
* }
* }
*
*/
public interface NativeNotificationContentFetcher {
/**
* Fetch notification content from external source
*
* This method is called by the plugin when:
* - Background fetch work is triggered (WorkManager)
* - Prefetch is scheduled before notification time
* - Manual refresh is requested (if native fetcher enabled)
*
* The plugin will:
* - Enforce a timeout (default 30 seconds, configurable via SchedulingPolicy)
* - Handle empty lists gracefully (no notifications scheduled)
* - Log errors for debugging
* - Retry on failure based on SchedulingPolicy
*
* @param context Context about why fetch was triggered, including
* trigger type, scheduled time, and optional metadata
* @return CompletableFuture that resolves to list of NotificationContent.
* Empty list indicates no content available (not an error).
* The future should complete exceptionally only on unrecoverable errors.
*/
@NonNull
CompletableFutureThis method is called by the plugin when {@code configureNativeFetcher} is invoked * from TypeScript. It provides a cross-platform mechanism for passing configuration * from the JavaScript layer to native code without using platform-specific storage * mechanisms.
* *When to implement:
*When to skip (use default no-op):
*Thread Safety: This method may be called from any thread. Implementations * must be thread-safe if storing configuration in instance variables.
* *Implementation Pattern:
*{@code
* 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 with API: " + apiBaseUrl);
* }
* }
*
* @param apiBaseUrl Base URL for API server. Examples:
* - Android emulator: "http://10.0.2.2:3000" (maps to host localhost:3000)
* - iOS simulator: "http://localhost:3000"
* - Production: "https://api.timesafari.com"
* @param activeDid Active DID (Decentralized Identifier) for authentication.
* Used as the JWT issuer/subject. Format: "did:ethr:0x..."
* @param jwtToken Pre-generated JWT token (ES256K signed) from TypeScript.
* This token is generated in the host app using TimeSafari's
* {@code createEndorserJwtForKey()} function. The native fetcher
* should use this token directly in the Authorization header as
* "Bearer {jwtToken}". No JWT generation or signing is needed in Java.
*
* @see DailyNotificationPlugin#configureNativeFetcher(PluginCall)
*/
default void configure(String apiBaseUrl, String activeDid, String jwtToken) {
// Default no-op implementation - fetchers that need config can override
// This allows fetchers that don't need TypeScript-provided configuration
// to ignore this method without implementing an empty body.
}
}