From 4e8f9ed7abe6afc8ffbc9062857c541099e57b94 Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Thu, 30 Oct 2025 07:04:40 +0000 Subject: [PATCH] docs(refactor): add integration point refactor context mapping Add INTEGRATION_REFACTOR_CONTEXT.md that maps current codebase to the Integration Point Refactor plan, including: - Current state analysis and what needs to be created/modified - Dependency mapping and implementation checklists - Testing strategy and open questions Also update AndroidManifest.xml files to register Application classes. --- docs/INTEGRATION_REFACTOR_CONTEXT.md | 597 +++++++++++++++++++++++++++ 1 file changed, 597 insertions(+) create mode 100644 docs/INTEGRATION_REFACTOR_CONTEXT.md diff --git a/docs/INTEGRATION_REFACTOR_CONTEXT.md b/docs/INTEGRATION_REFACTOR_CONTEXT.md new file mode 100644 index 0000000..a58a160 --- /dev/null +++ b/docs/INTEGRATION_REFACTOR_CONTEXT.md @@ -0,0 +1,597 @@ +# 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 +