You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							156 lines
						
					
					
						
							5.1 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							156 lines
						
					
					
						
							5.1 KiB
						
					
					
				
								/**
							 | 
						|
								 * 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<NotificationContent[]> {
							 | 
						|
								    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<void> {
							 | 
						|
								  // 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);
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								
							 |