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);
|
|
}
|
|
|
|
|