19 KiB
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)
- โ
NotificationContentinterface (matches directive spec) - โ
FetchContextinterface (matches directive spec) - โ
JsNotificationContentFetcherinterface (matches directive spec) - โ
FetchTriggertype (matches directive spec) - โ
SchedulingPolicyinterface (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
package com.timesafari.dailynotification;
public interface NativeNotificationContentFetcher {
java.util.concurrent.CompletableFuture<java.util.List<NotificationContent>>
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<String, Object> 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
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 policyDailyNotificationScheduler- 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:
@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:
export interface DailyNotificationPlugin {
// ... existing methods ...
setJsContentFetcher(fetcher: JsNotificationContentFetcher): void;
enableNativeFetcher(enable: boolean): Promise<void>;
setPolicy(policy: SchedulingPolicy): Promise<void>;
}
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:
-
Remove TimeSafari-Specific Coordination:
// Remove these lines (79-92): boolean timesafariCoordination = inputData.getBoolean("timesafari_coordination", false); // ... coordination checks ... -
Add Native Fetcher Call:
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<NotificationContent> results = nativeFetcher.fetchContent(context) .get(30, TimeUnit.SECONDS); return results.isEmpty() ? null : results.get(0); } catch (Exception e) { // Handle timeout, errors return null; } } -
Add SchedulingPolicy Backoff:
- Use
SchedulingPolicy.retryBackofffor retry delays - Replace hardcoded retry logic
- Use
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:
-
Store JS Fetcher Reference:
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 } -
Foreground Fetch Method:
@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
dedupeKeyvalues - Check
dedupeHorizonMswindow - Prevent duplicate notifications within horizon
- Use
dedupeKey(fallback toid)
Integration Points:
DailyNotificationScheduler.scheduleNotification()- check before schedulingDailyNotificationFetchWorker.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.cacheTtlSecondsor itemttlSeconds - Fallback to cached content if fetch fails and cache valid
3. Error Classification:
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:
-
DailyNotificationFetcher.scheduleFetch()(line ~77-116):- Enqueues
DailyNotificationFetchWorker - Currently generic (no TimeSafari coupling)
- Enqueues
-
TimeSafariIntegrationManager.fetchAndScheduleFromServer()(line ~258):- May enqueue workers with TimeSafari coordination flags
- Needs to switch to native fetcher path
-
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<NotificationContent>
โ DailyNotificationScheduler.scheduleNotification()
Target Flow:
NativeNotificationContentFetcher.fetchContent(context)
โ Returns List<NotificationContent> (already generic)
โ DailyNotificationScheduler.scheduleNotification()
Gap: convertBundleToNotificationContent() logic must move to host app (PR7).
3. TTL Enforcement Points
Current TTL Flow:
-
Hard-Fail at Arming:
->DailyNotificationScheduler.scheduleNotification()- Calls
ttlEnforcer.validateBeforeArming(content) - โ
Already generic (works with any
NotificationContent)
- Calls
-
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 observabilitypackages/polling-contracts/src/telemetry.ts- Telemetry manager
What to Add:
fetch_duration_ms- Time for native fetcher to completefetch_success- Boolean success/failurefetch_fail_class- Error classification (retryable/unretryable/partial/over-quota)items_fetched- Number of items from fetcheritems_enqueued- Number successfully scheduleddeduped_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:
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 releaseEnhancedDailyNotificationFetcher.java- Keep for one minor release- Legacy
@PluginMethodwrappers - Deprecate, keep for compatibility
File Creation Checklist
PR1: SPI Shell
android/plugin/src/main/java/com/timesafari/dailynotification/NativeNotificationContentFetcher.javaandroid/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
- Add
- Update
src/definitions.ts:- Wire
SchedulingPolicyfromcontent-fetcher.ts - Add new methods to
DailyNotificationPlugininterface
- Wire
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
- Implement
- 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
FetchErrorTypeenum - 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 fetcherSCHEDULING_POLICY.md- All configuration knobs- Update examples with real registration code
PR7: Feature Flag Legacy
- Add
USE_LEGACY_INTEGRATIONflag - Guard all TimeSafari code paths
- Update migration guide
- Regression tests
Open Questions to Resolve
Before PR1
-
Fetcher Registration Pattern:
- Should
setNativeFetcher()be called from host app'sApplication.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()
- Should
-
FetchContext Metadata:
- What should plugin populate in
metadatamap? - Current activeDid? App state? Network state?
- Recommendation: Start minimal (empty map), extend later
- What should plugin populate in
Before PR2
-
Background Worker Timeout:
- Current: 30 seconds for fetch
- Should this be configurable in
SchedulingPolicy? - Recommendation: Add
fetchTimeoutMstoSchedulingPolicyin PR1
-
Multiple NotificationContent Handling:
- If native fetcher returns multiple items, should all be scheduled?
- Should
maxBatchSizeapply here? - Recommendation: Schedule all valid items,
maxBatchSizelimits fetcher input/API calls
Before PR3
-
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 viasetJsContentFetchercallback)
-
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
- Review this context document - Verify completeness
- Resolve open questions - Make decisions before PR1
- Create PR1 branch - Start with SPI shell implementation
- Reference examples - Use
examples/native-fetcher-android.ktas pattern - 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