/** * JavaScript Content Fetcher Implementation (TypeScript) * * Example implementation of JsNotificationContentFetcher for foreground/manual refresh. * * NOTE: This is used ONLY for foreground operations. Background workers use * the Native Fetcher SPI (Kotlin/Swift) for reliability. * * @author Matthew Raymer * @version 1.0.0 */ import { DailyNotification } from '@timesafari/daily-notification-plugin'; import type { JsNotificationContentFetcher, FetchContext, NotificationContent } from '@timesafari/daily-notification-plugin'; import { TimeSafariAPI } from './timesafari-api'; // Host app's API client /** * TimeSafari JavaScript content fetcher * * Implements JsNotificationContentFetcher for foreground/manual refresh. * Background workers use the native Kotlin/Swift implementation. */ class TimeSafariJsFetcher implements JsNotificationContentFetcher { constructor( private api: TimeSafariAPI, private activeDid: string, private starredPlanIds: string[] ) {} async fetchContent(context: FetchContext): Promise { try { // 1. Generate JWT for authentication const jwt = await this.api.generateJWT(this.activeDid); // 2. Fetch TimeSafari data in parallel const [offersToPerson, offersToPlans, projectUpdates] = await Promise.all([ this.api.fetchOffersToPerson(jwt), this.api.fetchOffersToPlans(jwt), this.starredPlanIds.length > 0 ? this.api.fetchProjectsLastUpdated(this.starredPlanIds, jwt) : Promise.resolve(null) ]); // 3. Convert to NotificationContent array const contents: NotificationContent[] = []; // Convert offers to person if (offersToPerson?.data) { for (const offer of offersToPerson.data) { contents.push({ id: `offer_person_${offer.id}`, title: `New Offer: ${offer.title}`, body: offer.description || '', scheduledTime: context.scheduledTime || Date.now() + 3600000, fetchTime: context.fetchTime, mediaUrl: offer.imageUrl, ttlSeconds: 86400, // 24 hours dedupeKey: `offer_person_${offer.id}_${offer.updatedAt}`, priority: 'default', metadata: { offerId: offer.id, issuerDid: offer.issuerDid, source: 'offers_to_person' } }); } } // Convert offers to plans if (offersToPlans?.data) { for (const offer of offersToPlans.data) { contents.push({ id: `offer_plan_${offer.id}`, title: `Offer for Your Project: ${offer.projectName}`, body: offer.description || '', scheduledTime: context.scheduledTime || Date.now() + 3600000, fetchTime: context.fetchTime, mediaUrl: offer.imageUrl, ttlSeconds: 86400, dedupeKey: `offer_plan_${offer.id}_${offer.updatedAt}`, priority: 'default', metadata: { offerId: offer.id, planHandleId: offer.planHandleId, source: 'offers_to_plans' } }); } } // Convert project updates if (projectUpdates?.data) { for (const update of projectUpdates.data) { contents.push({ id: `project_${update.planSummary.jwtId}`, title: `${update.planSummary.name} Updated`, body: `New updates for ${update.planSummary.name}`, scheduledTime: context.scheduledTime || Date.now() + 3600000, fetchTime: context.fetchTime, mediaUrl: update.planSummary.image, ttlSeconds: 86400, dedupeKey: `project_${update.planSummary.handleId}_${update.planSummary.jwtId}`, priority: 'default', metadata: { planHandleId: update.planSummary.handleId, jwtId: update.planSummary.jwtId, source: 'project_updates' } }); } } return contents; } catch (error) { console.error('TimeSafari fetch failed:', error); // Return empty array - plugin will handle retry based on policy return []; } } } /** * Register fetcher and configure policy */ export async function setupDailyNotification( api: TimeSafariAPI, activeDid: string, starredPlanIds: string[] ): Promise { // Create JS fetcher for foreground operations const jsFetcher = new TimeSafariJsFetcher(api, activeDid, starredPlanIds); DailyNotification.setJsContentFetcher(jsFetcher); // Configure scheduling policy await DailyNotification.setPolicy({ prefetchWindowMs: 5 * 60 * 1000, // 5 minutes before scheduled time retryBackoff: { minMs: 2000, maxMs: 600000, // 10 minutes max factor: 2, jitterPct: 20 }, maxBatchSize: 50, dedupeHorizonMs: 24 * 60 * 60 * 1000, // 24 hours cacheTtlSeconds: 6 * 60 * 60, // 6 hours default exactAlarmsAllowed: true // If app has permission }); // Enable native fetcher (required for background workers) await DailyNotification.enableNativeFetcher(true); }