# Integration Point Refactor - Implementation Context **Author**: Matthew Raymer **Date**: 2025-10-29 **Status**: 🎯 **CONTEXT** - Pre-implementation analysis and mapping ## Purpose This document maps the current codebase to the Integration Point Refactor plan, identifies what exists, what needs to be created, and where gaps exist before starting implementation (PR1). --- ## Current State Analysis ### ✅ What Already Exists #### 1. TypeScript Type Definitions (`src/types/content-fetcher.ts`) - ✅ `NotificationContent` interface (matches directive spec) - ✅ `FetchContext` interface (matches directive spec) - ✅ `JsNotificationContentFetcher` interface (matches directive spec) - ✅ `FetchTrigger` type (matches directive spec) - ✅ `SchedulingPolicy` interface (matches directive spec) **Status**: Complete and ready for PR1. No changes needed. #### 2. Example Implementations - ✅ `examples/native-fetcher-android.kt` - Kotlin native fetcher skeleton - ✅ `examples/js-fetcher-typescript.ts` - TypeScript JS fetcher skeleton **Status**: Examples exist as reference. These are for host app, not plugin. #### 3. Test Contract (`tests/fixtures/test-contract.json`) - ✅ Golden contract fixture with expected outputs - ✅ Failure scenarios defined **Status**: Ready for contract tests. Will be used in PR2+ for validation. #### 4. Current TimeSafari Integration (To Be Replaced) **Location**: `android/plugin/src/main/java/com/timesafari/dailynotification/` - ✅ `TimeSafariIntegrationManager.java` - Current TimeSafari orchestration - ✅ `EnhancedDailyNotificationFetcher.java` - TimeSafari API client - ✅ `DailyNotificationJWTManager.java` - JWT generation for TimeSafari - ✅ `DailyNotificationETagManager.java` - ETag caching for TimeSafari **Status**: These will be moved to host app OR deprecated behind feature flag (PR7). **Current Integration Flow**: ``` DailyNotificationPlugin.load() → TimeSafariIntegrationManager (created in plugin) → EnhancedDailyNotificationFetcher → JWTManager + ETagManager → TimeSafari API endpoints ``` **Target Flow** (after refactor): ``` DailyNotificationPlugin.load() → NativeNotificationContentFetcher (registered by host app) → Host app's TimeSafari integration → TimeSafari API endpoints ``` --- ## What Needs to Be Created ### PR1: SPI Shell (First Implementation) #### 1. Android Native SPI Interface **Location**: `android/plugin/src/main/java/com/timesafari/dailynotification/` **File to Create**: `NativeNotificationContentFetcher.java` ```java package com.timesafari.dailynotification; public interface NativeNotificationContentFetcher { java.util.concurrent.CompletableFuture> fetchContent(FetchContext context); } public class FetchContext { public final String trigger; // "background_work" | "prefetch" | "manual" | "scheduled" public final Long scheduledTime; // Optional: epoch ms public final long fetchTime; // Required: epoch ms public final Map metadata; // Optional // Constructor, getters... } ``` **Status**: ❌ Not created yet - PR1 task #### 2. Fetcher Registry in Plugin **Location**: `android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationPlugin.java` **What to Add**: - Field: `private NativeNotificationContentFetcher nativeFetcher;` - Method: `public void setNativeFetcher(NativeNotificationContentFetcher fetcher)` - Storage: Store in static registry or application-level singleton **Status**: ❌ Not implemented yet - PR1 task #### 3. SchedulingPolicy Implementation (Android) **Location**: `android/plugin/src/main/java/com/timesafari/dailynotification/` **File to Create**: `SchedulingPolicy.java` ```java public class SchedulingPolicy { public Long prefetchWindowMs; public RetryBackoff retryBackoff; public Integer maxBatchSize; public Long dedupeHorizonMs; public Integer cacheTtlSeconds; public Boolean exactAlarmsAllowed; public static class RetryBackoff { public long minMs; public long maxMs; public double factor; public int jitterPct; } } ``` **Status**: ❌ Not created yet - PR1 task **Where Used**: - `DailyNotificationFetchWorker` - backoff policy - `DailyNotificationScheduler` - prefetch timing - Deduplication logic (new, PR4) - TTL enforcement (existing `DailyNotificationTTLEnforcer`) #### 4. Plugin API Methods (TypeScript Bridge) **Location**: `android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationPlugin.java` **Methods to Add**: ```java @PluginMethod public void setJsContentFetcher(PluginCall call) { // Store JS fetcher for foreground use // Will be called from TypeScript } @PluginMethod public void enableNativeFetcher(PluginCall call) { // Toggle native fetcher on/off // Default: true (native required for background) } @PluginMethod public void setPolicy(PluginCall call) { // Update SchedulingPolicy // Parse JSON from call, create SchedulingPolicy instance } ``` **Status**: ❌ Not implemented yet - PR1 task #### 5. TypeScript Plugin Interface Extensions **Location**: `src/definitions.ts` **What to Add**: ```typescript export interface DailyNotificationPlugin { // ... existing methods ... setJsContentFetcher(fetcher: JsNotificationContentFetcher): void; enableNativeFetcher(enable: boolean): Promise; setPolicy(policy: SchedulingPolicy): Promise; } ``` **Status**: ❌ Partially exists in `src/types/content-fetcher.ts` but not wired to `DailyNotificationPlugin` interface in `src/definitions.ts` - PR1 task --- ### PR2: Background Workers #### Current Worker: `DailyNotificationFetchWorker.java` **Location**: `android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationFetchWorker.java` **Current Behavior**: - Uses `DailyNotificationFetcher` (generic, no TimeSafari) - Called from `DailyNotificationFetcher.scheduleFetch()` - Runs via WorkManager **What Needs to Change**: 1. **Remove TimeSafari-Specific Coordination**: ```java // Remove these lines (79-92): boolean timesafariCoordination = inputData.getBoolean("timesafari_coordination", false); // ... coordination checks ... ``` 2. **Add Native Fetcher Call**: ```java private NotificationContent fetchContentWithTimeout() { if (nativeFetcher == null) { Log.w(TAG, "Native fetcher not registered, falling back to cache"); return getCachedContent(); } FetchContext context = new FetchContext( "background_work", scheduledTime, System.currentTimeMillis(), metadata ); try { List results = nativeFetcher.fetchContent(context) .get(30, TimeUnit.SECONDS); return results.isEmpty() ? null : results.get(0); } catch (Exception e) { // Handle timeout, errors return null; } } ``` 3. **Add SchedulingPolicy Backoff**: - Use `SchedulingPolicy.retryBackoff` for retry delays - Replace hardcoded retry logic **Status**: ❌ Not modified yet - PR2 task --- ### PR3: JS Fetcher Path #### Plugin Side: Bridge JS Fetcher to Workers **Location**: `android/plugin/src/main/java/com/timesafari/dailynotification/DailyNotificationPlugin.java` **What Needs to Change**: 1. **Store JS Fetcher Reference**: ```java private JsNotificationContentFetcher jsFetcher; // Wrapper object needed @PluginMethod public void setJsContentFetcher(PluginCall call) { // Extract callback from JS // Store for foreground use only // Log warning if used in background worker } ``` 2. **Foreground Fetch Method**: ```java @PluginMethod public void refreshNow(PluginCall call) { if (jsFetcher != null) { // Call JS fetcher (foreground only) // Must bridge JS callback to Java } else if (nativeFetcher != null) { // Fall back to native fetcher } } ``` **Challenge**: JavaScript → Java callback bridge. Options: - Option A: Use Capacitor's bridge to call JS function - Option B: Use event system (plugin emits event, JS responds) - Option C: Store fetcher config, not callback (requires HTTP endpoint) **Status**: ❌ Not implemented yet - PR3 task --- ### PR4: Deduplication #### New Component: Deduplication Store **Location**: `android/plugin/src/main/java/com/timesafari/dailynotification/` **File to Create**: `NotificationDeduplicationStore.java` **Responsibilities**: - Maintain bloom filter or LRU cache of recent `dedupeKey` values - Check `dedupeHorizonMs` window - Prevent duplicate notifications within horizon - Use `dedupeKey` (fallback to `id`) **Integration Points**: - `DailyNotificationScheduler.scheduleNotification()` - check before scheduling - `DailyNotificationFetchWorker.doWork()` - check after fetch **Status**: ❌ Not created yet - PR4 task --- ### PR5: Failure Policy #### Enhance Existing Components **Location**: Multiple files **1. Retry Logic** (`DailyNotificationFetchWorker.java`): - Replace hardcoded retry with `SchedulingPolicy.retryBackoff` - Implement exponential backoff with jitter - Distinguish retryable vs unretryable errors **2. Cache Fallback** (new or enhance `DailyNotificationStorage.java`): - Check TTL using `SchedulingPolicy.cacheTtlSeconds` or item `ttlSeconds` - Fallback to cached content if fetch fails and cache valid **3. Error Classification**: ```java public enum FetchErrorType { RETRYABLE, // 5xx, network, timeout UNRETRYABLE, // 4xx auth, schema error PARTIAL, // Some items bad OVER_QUOTA // 429 with Retry-After } ``` **Status**: ❌ Partially implemented (basic retry exists), needs enhancement - PR5 task --- ## Component Dependency Map ### Current Dependencies ``` DailyNotificationPlugin ├── TimeSafariIntegrationManager (TO BE REMOVED in PR7) │ ├── EnhancedDailyNotificationFetcher (TO BE MOVED to host app) │ ├── DailyNotificationJWTManager (TO BE MOVED to host app) │ └── DailyNotificationETagManager (TO BE MOVED to host app) ├── DailyNotificationScheduler │ ├── DailyNotificationTTLEnforcer │ └── PendingIntentManager ├── DailyNotificationFetchWorker │ └── DailyNotificationFetcher (generic, keep) └── DailyNotificationStorage ``` ### Target Dependencies (After Refactor) ``` DailyNotificationPlugin ├── NativeNotificationContentFetcher (REGISTERED by host app) ├── JsNotificationContentFetcher (REGISTERED by host app, foreground only) ├── SchedulingPolicy (CONFIGURED by host app) ├── DailyNotificationScheduler │ ├── DailyNotificationTTLEnforcer │ ├── PendingIntentManager │ └── NotificationDeduplicationStore (NEW in PR4) ├── DailyNotificationFetchWorker │ ├── Calls NativeNotificationContentFetcher (native path) │ └── Uses SchedulingPolicy (backoff, TTL) └── DailyNotificationStorage ``` --- ## Integration Points to Identify ### 1. Worker Enqueue Points **Where Workers Are Scheduled**: 1. **`DailyNotificationFetcher.scheduleFetch()`** (line ~77-116): - Enqueues `DailyNotificationFetchWorker` - Currently generic (no TimeSafari coupling) 2. **`TimeSafariIntegrationManager.fetchAndScheduleFromServer()`** (line ~258): - May enqueue workers with TimeSafari coordination flags - Needs to switch to native fetcher path 3. **`DailyNotificationPlugin.scheduleCoordinatedBackgroundJobs()`**: - Phase 3 coordination code - Adds TimeSafari-specific flags to WorkManager Data - Needs refactor to remove TimeSafari flags **Action for PR2**: Modify worker enqueue to use native fetcher, remove TimeSafari coordination flags. --- ### 2. Notification Content Flow **Current Flow**: ``` EnhancedDailyNotificationFetcher.fetch() → Returns TimeSafariNotificationBundle → TimeSafariIntegrationManager.convertBundleToNotificationContent() → Returns List → DailyNotificationScheduler.scheduleNotification() ``` **Target Flow**: ``` NativeNotificationContentFetcher.fetchContent(context) → Returns List (already generic) → DailyNotificationScheduler.scheduleNotification() ``` **Gap**: `convertBundleToNotificationContent()` logic must move to host app (PR7). --- ### 3. TTL Enforcement Points **Current TTL Flow**: 1. **Hard-Fail at Arming**: `->DailyNotificationScheduler.scheduleNotification()` - Calls `ttlEnforcer.validateBeforeArming(content)` - ✅ Already generic (works with any `NotificationContent`) 2. **Soft-Check at Fire**: `DailyNotificationReceiver.onReceive()` - Checks freshness, may trigger refresh - ✅ Already generic **Status**: ✅ TTL enforcement is already generic - no changes needed for SPI. --- ### 4. Metrics Collection Points **Current Metrics**: - `src/observability.ts` - TypeScript observability - `packages/polling-contracts/src/telemetry.ts` - Telemetry manager **What to Add**: - `fetch_duration_ms` - Time for native fetcher to complete - `fetch_success` - Boolean success/failure - `fetch_fail_class` - Error classification (retryable/unretryable/partial/over-quota) - `items_fetched` - Number of items from fetcher - `items_enqueued` - Number successfully scheduled - `deduped_count` - Items filtered by deduplication (PR4) - `cache_hits` - Cached content used when fetch fails (PR5) **Action for PR2**: Add metrics calls in `DailyNotificationFetchWorker.doWork()`. --- ## Migration Path Mapping ### Feature Flag Implementation (PR7) **Location**: `DailyNotificationPlugin.java` **What to Add**: ```java private static final boolean USE_LEGACY_INTEGRATION = false; // Default false in new minor private void fetchContent() { if (USE_LEGACY_INTEGRATION && timeSafariIntegration != null) { // Legacy path timeSafariIntegration.fetchAndScheduleFromServer(false); } else if (nativeFetcher != null) { // New SPI path // Call native fetcher } else { Log.w(TAG, "No fetcher available"); } } ``` **Files to Guard**: - `TimeSafariIntegrationManager.java` - Keep for one minor release - `EnhancedDailyNotificationFetcher.java` - Keep for one minor release - Legacy `@PluginMethod` wrappers - Deprecate, keep for compatibility --- ## File Creation Checklist ### PR1: SPI Shell - [ ] `android/plugin/src/main/java/com/timesafari/dailynotification/NativeNotificationContentFetcher.java` - [ ] `android/plugin/src/main/java/com/timesafari/dailynotification/FetchContext.java` (or inner class) - [ ] `android/plugin/src/main/java/com/timesafari/dailynotification/SchedulingPolicy.java` - [ ] Update `DailyNotificationPlugin.java`: - [ ] Add `setNativeFetcher()` method - [ ] Add `setJsContentFetcher()` method (stub, full in PR3) - [ ] Add `enableNativeFetcher()` method - [ ] Add `setPolicy()` method - [ ] Update `src/definitions.ts`: - [ ] Wire `SchedulingPolicy` from `content-fetcher.ts` - [ ] Add new methods to `DailyNotificationPlugin` interface ### PR2: Background Workers - [ ] Modify `DailyNotificationFetchWorker.java`: - [ ] Remove TimeSafari coordination checks - [ ] Add native fetcher call - [ ] Add SchedulingPolicy backoff - [ ] Add metrics recording ### PR3: JS Fetcher Path - [ ] Modify `DailyNotificationPlugin.java`: - [ ] Implement `setJsContentFetcher()` (JS bridge) - [ ] Add foreground refresh method - [ ] Add event bus for JS communication - [ ] Create TypeScript bridge utilities ### PR4: Deduplication - [ ] `android/plugin/src/main/java/com/timesafari/dailynotification/NotificationDeduplicationStore.java` - [ ] Integrate into `DailyNotificationScheduler` - [ ] Add metrics for deduped count ### PR5: Failure Policy - [ ] Create `FetchErrorType` enum - [ ] Enhance retry logic with SchedulingPolicy - [ ] Add cache fallback mechanism - [ ] Add error classification helpers ### PR6: Docs & Samples - [ ] `INTEGRATION_GUIDE.md` - When to use native vs JS fetcher - [ ] `SCHEDULING_POLICY.md` - All configuration knobs - [ ] Update examples with real registration code ### PR7: Feature Flag Legacy - [ ] Add `USE_LEGACY_INTEGRATION` flag - [ ] Guard all TimeSafari code paths - [ ] Update migration guide - [ ] Regression tests --- ## Open Questions to Resolve ### Before PR1 1. **Fetcher Registration Pattern**: - Should `setNativeFetcher()` be called from host app's `Application.onCreate()`? - Or should it be a static registry accessible from host app's native code? - **Recommendation**: Static registry in plugin, host app registers in `Application.onCreate()` 2. **FetchContext Metadata**: - What should plugin populate in `metadata` map? - Current activeDid? App state? Network state? - **Recommendation**: Start minimal (empty map), extend later ### Before PR2 3. **Background Worker Timeout**: - Current: 30 seconds for fetch - Should this be configurable in `SchedulingPolicy`? - **Recommendation**: Add `fetchTimeoutMs` to `SchedulingPolicy` in PR1 4. **Multiple NotificationContent Handling**: - If native fetcher returns multiple items, should all be scheduled? - Should `maxBatchSize` apply here? - **Recommendation**: Schedule all valid items, `maxBatchSize` limits fetcher input/API calls ### Before PR3 5. **JS Fetcher Bridge Mechanism**: - Which approach: Capacitor bridge callback, event system, or HTTP endpoint? - **Recommendation**: Start with event system (plugin emits `fetch_needed`, JS responds via `setJsContentFetcher` callback) 6. **Foreground-Only Enforcement**: - How to prevent JS fetcher from being called in background worker? - **Recommendation**: Runtime guard + lint rule (document in PR3) --- ## Testing Strategy ### Contract Tests (After PR2) - Use `tests/fixtures/test-contract.json` - Run native fetcher with fixture context - Compare normalized output to expected output - **Location**: Create `android/plugin/src/test/java/com/timesafari/dailynotification/NativeFetcherContractTest.java` ### Integration Tests (After PR3) - Test JS fetcher in foreground - Test native fetcher in background worker - Verify both produce identical results for same input ### Regression Tests (Before PR7) - Ensure legacy TimeSafari path still works behind feature flag - Verify new SPI path works independently - Test migration from legacy to SPI path --- ## Next Steps 1. **Review this context document** - Verify completeness 2. **Resolve open questions** - Make decisions before PR1 3. **Create PR1 branch** - Start with SPI shell implementation 4. **Reference examples** - Use `examples/native-fetcher-android.kt` as pattern 5. **Follow 7-PR plan** - Sequential implementation with tests after each PR --- **Status**: Context building complete - ready for PR1 implementation **Last Updated**: 2025-10-29 **Maintainer**: Plugin development team