docs(refactor): add integration point refactor analysis and implementation plan
Add comprehensive documentation and implementation artifacts for refactoring the plugin to use app-provided content fetchers instead of hardcoded TimeSafari integration. Changes: - Add integration-point-refactor-analysis.md with complete ADR, interfaces, migration plan, and 7-PR breakdown - Add INTEGRATION_REFACTOR_QUICK_START.md for quick reference on new machines - Add src/types/content-fetcher.ts with TypeScript SPI interfaces - Add examples/native-fetcher-android.kt with Kotlin implementation skeleton - Add examples/js-fetcher-typescript.ts with TypeScript implementation skeleton - Add tests/fixtures/test-contract.json for golden contract testing Architecture Decisions: - Dual-path SPI: Native Fetcher (background) + JS Fetcher (foreground only) - Background reliability: Native SPI only, no JS bridging in workers - Reversibility: Legacy code behind feature flag for one minor release - Test contract: Single JSON fixture for both fetcher paths This provides complete specification for implementing the refactor in 7 PRs, starting with SPI shell and progressing through background workers, deduplication, failure policies, and finally legacy code removal. All documentation is self-contained and ready for implementation on any machine.
This commit is contained in:
153
examples/native-fetcher-android.kt
Normal file
153
examples/native-fetcher-android.kt
Normal file
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* TimeSafari Native Fetcher Implementation (Android/Kotlin)
|
||||
*
|
||||
* Example implementation of NativeNotificationContentFetcher for Android.
|
||||
* This runs in background workers and does NOT require JavaScript bridge.
|
||||
*
|
||||
* @author Matthew Raymer
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
package com.timesafari.notification
|
||||
|
||||
import com.timesafari.dailynotification.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* TimeSafari native content fetcher
|
||||
*
|
||||
* Implements the NativeNotificationContentFetcher SPI to provide
|
||||
* TimeSafari-specific notification content fetching for background workers.
|
||||
*/
|
||||
class TimeSafariNativeFetcher(
|
||||
private val api: TimeSafariApi,
|
||||
private val tokenProvider: TokenProvider,
|
||||
private val starredPlanIds: List<String>
|
||||
) : NativeNotificationContentFetcher {
|
||||
|
||||
override suspend fun fetchContent(context: FetchContext): List<NotificationContent> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
try {
|
||||
// 1. Get fresh authentication token
|
||||
val jwt = tokenProvider.freshToken()
|
||||
|
||||
// 2. Fetch TimeSafari data in parallel
|
||||
val offersToPerson = async { api.fetchOffersToPerson(jwt) }
|
||||
val offersToPlans = async { api.fetchOffersToPlans(jwt) }
|
||||
val projectUpdates = async {
|
||||
if (starredPlanIds.isNotEmpty()) {
|
||||
api.fetchProjectsLastUpdated(starredPlanIds, jwt)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for all requests
|
||||
val offersPersonResult = offersToPerson.await()
|
||||
val offersPlansResult = offersToPlans.await()
|
||||
val updatesResult = projectUpdates.await()
|
||||
|
||||
// 3. Convert to NotificationContent list
|
||||
buildList {
|
||||
// Add offers to person
|
||||
offersPersonResult?.data?.forEach { offer ->
|
||||
add(
|
||||
NotificationContent(
|
||||
id = "offer_person_${offer.id}",
|
||||
title = "New Offer: ${offer.title}",
|
||||
body = offer.description ?: "",
|
||||
scheduledTime = context.scheduledTime
|
||||
?: (System.currentTimeMillis() + 3_600_000),
|
||||
fetchTime = context.fetchTime,
|
||||
mediaUrl = offer.imageUrl,
|
||||
ttlSeconds = 86_400, // 24 hours
|
||||
dedupeKey = "offer_person_${offer.id}_${offer.updatedAt}",
|
||||
priority = "default",
|
||||
metadata = mapOf(
|
||||
"offerId" to offer.id,
|
||||
"issuerDid" to offer.issuerDid,
|
||||
"source" to "offers_to_person"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Add offers to plans
|
||||
offersPlansResult?.data?.forEach { offer ->
|
||||
add(
|
||||
NotificationContent(
|
||||
id = "offer_plan_${offer.id}",
|
||||
title = "Offer for Your Project: ${offer.projectName}",
|
||||
body = offer.description ?: "",
|
||||
scheduledTime = context.scheduledTime
|
||||
?: (System.currentTimeMillis() + 3_600_000),
|
||||
fetchTime = context.fetchTime,
|
||||
mediaUrl = offer.imageUrl,
|
||||
ttlSeconds = 86_400,
|
||||
dedupeKey = "offer_plan_${offer.id}_${offer.updatedAt}",
|
||||
priority = "default",
|
||||
metadata = mapOf(
|
||||
"offerId" to offer.id,
|
||||
"planHandleId" to offer.planHandleId,
|
||||
"source" to "offers_to_plans"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Add project updates
|
||||
updatesResult?.data?.forEach { update ->
|
||||
add(
|
||||
NotificationContent(
|
||||
id = "project_${update.planSummary.jwtId}",
|
||||
title = "${update.planSummary.name} Updated",
|
||||
body = "New updates for ${update.planSummary.name}",
|
||||
scheduledTime = context.scheduledTime
|
||||
?: (System.currentTimeMillis() + 3_600_000),
|
||||
fetchTime = context.fetchTime,
|
||||
mediaUrl = update.planSummary.image,
|
||||
ttlSeconds = 86_400,
|
||||
dedupeKey = "project_${update.planSummary.handleId}_${update.planSummary.jwtId}",
|
||||
priority = "default",
|
||||
metadata = mapOf(
|
||||
"planHandleId" to update.planSummary.handleId,
|
||||
"jwtId" to update.planSummary.jwtId,
|
||||
"source" to "project_updates"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// Log error and return empty list
|
||||
// Plugin will handle retry based on SchedulingPolicy
|
||||
android.util.Log.e("TimeSafariFetcher", "Fetch failed", e)
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registration in Application.onCreate()
|
||||
*/
|
||||
class TimeSafariApplication : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
// Register native fetcher
|
||||
val api = TimeSafariApiClient(context = this)
|
||||
val tokenProvider = JWTokenProvider(activeDid = getActiveDid())
|
||||
val starredPlanIds = getStarredPlanIds()
|
||||
|
||||
val fetcher = TimeSafariNativeFetcher(
|
||||
api = api,
|
||||
tokenProvider = tokenProvider,
|
||||
starredPlanIds = starredPlanIds
|
||||
)
|
||||
|
||||
DailyNotification.setNativeFetcher(fetcher)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user