Browse Source
- Add comprehensive configuration system with timesafari-config.json - Create shared config-loader.ts with TypeScript interfaces and mock services - Update Android test app to use TimeSafari community notification patterns - Update iOS test app with rolling window and community features - Update Electron test app with desktop-specific TimeSafari integration - Enhance test API server to simulate Endorser.ch API endpoints - Add pagination support with afterId/beforeId parameters - Implement parallel API requests pattern for offers, projects, people, items - Add community analytics and notification bundle endpoints - Update all test app UIs for TimeSafari-specific functionality - Update README with comprehensive TimeSafari testing guide All test apps now demonstrate: - Real Endorser.ch API integration patterns - TimeSafari community-building features - Platform-specific optimizations (Android/iOS/Electron) - Comprehensive error handling and performance monitoring - Configuration-driven testing with type safetyresearch/notification-plugin-enhancement
11 changed files with 3431 additions and 388 deletions
File diff suppressed because it is too large
@ -0,0 +1,152 @@ |
|||||
|
{ |
||||
|
"timesafari": { |
||||
|
"appId": "app.timesafari.test", |
||||
|
"appName": "TimeSafari Test", |
||||
|
"version": "1.0.0", |
||||
|
"description": "Test app for TimeSafari Daily Notification Plugin integration" |
||||
|
}, |
||||
|
"endorser": { |
||||
|
"baseUrl": "http://localhost:3001", |
||||
|
"apiVersion": "v2", |
||||
|
"endpoints": { |
||||
|
"offers": "/api/v2/report/offers", |
||||
|
"offersToPlans": "/api/v2/report/offersToPlansOwnedByMe", |
||||
|
"plansLastUpdated": "/api/v2/report/plansLastUpdatedBetween", |
||||
|
"notificationsBundle": "/api/v2/report/notifications/bundle" |
||||
|
}, |
||||
|
"authentication": { |
||||
|
"type": "Bearer", |
||||
|
"token": "test-jwt-token-12345", |
||||
|
"headers": { |
||||
|
"Authorization": "Bearer test-jwt-token-12345", |
||||
|
"Content-Type": "application/json", |
||||
|
"X-Privacy-Level": "user-controlled" |
||||
|
} |
||||
|
}, |
||||
|
"pagination": { |
||||
|
"defaultLimit": 50, |
||||
|
"maxLimit": 100, |
||||
|
"hitLimitThreshold": 50 |
||||
|
} |
||||
|
}, |
||||
|
"notificationTypes": { |
||||
|
"offers": { |
||||
|
"enabled": true, |
||||
|
"types": [ |
||||
|
"new_to_me", |
||||
|
"changed_to_me", |
||||
|
"new_to_projects", |
||||
|
"changed_to_projects", |
||||
|
"new_to_favorites", |
||||
|
"changed_to_favorites" |
||||
|
] |
||||
|
}, |
||||
|
"projects": { |
||||
|
"enabled": true, |
||||
|
"types": [ |
||||
|
"local_new", |
||||
|
"local_changed", |
||||
|
"content_interest_new", |
||||
|
"favorited_changed" |
||||
|
] |
||||
|
}, |
||||
|
"people": { |
||||
|
"enabled": true, |
||||
|
"types": [ |
||||
|
"local_new", |
||||
|
"local_changed", |
||||
|
"content_interest_new", |
||||
|
"favorited_changed", |
||||
|
"contacts_changed" |
||||
|
] |
||||
|
}, |
||||
|
"items": { |
||||
|
"enabled": true, |
||||
|
"types": [ |
||||
|
"local_new", |
||||
|
"local_changed", |
||||
|
"favorited_changed" |
||||
|
] |
||||
|
} |
||||
|
}, |
||||
|
"scheduling": { |
||||
|
"contentFetch": { |
||||
|
"schedule": "0 8 * * *", |
||||
|
"time": "08:00", |
||||
|
"description": "8 AM daily - fetch community updates" |
||||
|
}, |
||||
|
"userNotification": { |
||||
|
"schedule": "0 9 * * *", |
||||
|
"time": "09:00", |
||||
|
"description": "9 AM daily - notify users of community updates" |
||||
|
} |
||||
|
}, |
||||
|
"testData": { |
||||
|
"userDid": "did:example:testuser123", |
||||
|
"starredPlanIds": [ |
||||
|
"plan-community-garden", |
||||
|
"plan-local-food", |
||||
|
"plan-sustainability" |
||||
|
], |
||||
|
"lastKnownOfferId": "01HSE3R9MAC0FT3P3KZ382TWV7", |
||||
|
"lastKnownPlanId": "01HSE3R9MAC0FT3P3KZ382TWV8", |
||||
|
"mockOffers": [ |
||||
|
{ |
||||
|
"jwtId": "01HSE3R9MAC0FT3P3KZ382TWV7", |
||||
|
"handleId": "offer-web-dev-001", |
||||
|
"offeredByDid": "did:example:offerer123", |
||||
|
"recipientDid": "did:example:testuser123", |
||||
|
"objectDescription": "Web development services for community project", |
||||
|
"unit": "USD", |
||||
|
"amount": 1000, |
||||
|
"amountGiven": 500, |
||||
|
"amountGivenConfirmed": 250 |
||||
|
} |
||||
|
], |
||||
|
"mockProjects": [ |
||||
|
{ |
||||
|
"plan": { |
||||
|
"jwtId": "01HSE3R9MAC0FT3P3KZ382TWV8", |
||||
|
"handleId": "plan-community-garden", |
||||
|
"name": "Community Garden Project", |
||||
|
"description": "Building a community garden for local food production", |
||||
|
"issuerDid": "did:example:issuer123", |
||||
|
"agentDid": "did:example:agent123" |
||||
|
}, |
||||
|
"wrappedClaimBefore": null |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
"callbacks": { |
||||
|
"offers": { |
||||
|
"enabled": true, |
||||
|
"localHandler": "handleOffersNotification" |
||||
|
}, |
||||
|
"projects": { |
||||
|
"enabled": true, |
||||
|
"localHandler": "handleProjectsNotification" |
||||
|
}, |
||||
|
"people": { |
||||
|
"enabled": true, |
||||
|
"localHandler": "handlePeopleNotification" |
||||
|
}, |
||||
|
"items": { |
||||
|
"enabled": true, |
||||
|
"localHandler": "handleItemsNotification" |
||||
|
}, |
||||
|
"communityAnalytics": { |
||||
|
"enabled": true, |
||||
|
"endpoint": "http://localhost:3001/api/analytics/community-events", |
||||
|
"headers": { |
||||
|
"Content-Type": "application/json", |
||||
|
"X-Privacy-Level": "aggregated" |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"observability": { |
||||
|
"enableLogging": true, |
||||
|
"logLevel": "debug", |
||||
|
"enableMetrics": true, |
||||
|
"enableHealthChecks": true |
||||
|
} |
||||
|
} |
@ -0,0 +1,522 @@ |
|||||
|
/** |
||||
|
* Configuration loader for TimeSafari test apps |
||||
|
* |
||||
|
* Loads configuration from JSON files and provides typed access |
||||
|
* to TimeSafari-specific settings, Endorser.ch API endpoints, |
||||
|
* and test data. |
||||
|
* |
||||
|
* @author Matthew Raymer |
||||
|
* @version 1.0.0 |
||||
|
*/ |
||||
|
|
||||
|
export interface TimeSafariConfig { |
||||
|
timesafari: { |
||||
|
appId: string; |
||||
|
appName: string; |
||||
|
version: string; |
||||
|
description: string; |
||||
|
}; |
||||
|
endorser: { |
||||
|
baseUrl: string; |
||||
|
apiVersion: string; |
||||
|
endpoints: { |
||||
|
offers: string; |
||||
|
offersToPlans: string; |
||||
|
plansLastUpdated: string; |
||||
|
notificationsBundle: string; |
||||
|
}; |
||||
|
authentication: { |
||||
|
type: string; |
||||
|
token: string; |
||||
|
headers: Record<string, string>; |
||||
|
}; |
||||
|
pagination: { |
||||
|
defaultLimit: number; |
||||
|
maxLimit: number; |
||||
|
hitLimitThreshold: number; |
||||
|
}; |
||||
|
}; |
||||
|
notificationTypes: { |
||||
|
offers: { |
||||
|
enabled: boolean; |
||||
|
types: string[]; |
||||
|
}; |
||||
|
projects: { |
||||
|
enabled: boolean; |
||||
|
types: string[]; |
||||
|
}; |
||||
|
people: { |
||||
|
enabled: boolean; |
||||
|
types: string[]; |
||||
|
}; |
||||
|
items: { |
||||
|
enabled: boolean; |
||||
|
types: string[]; |
||||
|
}; |
||||
|
}; |
||||
|
scheduling: { |
||||
|
contentFetch: { |
||||
|
schedule: string; |
||||
|
time: string; |
||||
|
description: string; |
||||
|
}; |
||||
|
userNotification: { |
||||
|
schedule: string; |
||||
|
time: string; |
||||
|
description: string; |
||||
|
}; |
||||
|
}; |
||||
|
testData: { |
||||
|
userDid: string; |
||||
|
starredPlanIds: string[]; |
||||
|
lastKnownOfferId: string; |
||||
|
lastKnownPlanId: string; |
||||
|
mockOffers: any[]; |
||||
|
mockProjects: any[]; |
||||
|
}; |
||||
|
callbacks: { |
||||
|
offers: { |
||||
|
enabled: boolean; |
||||
|
localHandler: string; |
||||
|
}; |
||||
|
projects: { |
||||
|
enabled: boolean; |
||||
|
localHandler: string; |
||||
|
}; |
||||
|
people: { |
||||
|
enabled: boolean; |
||||
|
localHandler: string; |
||||
|
}; |
||||
|
items: { |
||||
|
enabled: boolean; |
||||
|
localHandler: string; |
||||
|
}; |
||||
|
communityAnalytics: { |
||||
|
enabled: boolean; |
||||
|
endpoint: string; |
||||
|
headers: Record<string, string>; |
||||
|
}; |
||||
|
}; |
||||
|
observability: { |
||||
|
enableLogging: boolean; |
||||
|
logLevel: string; |
||||
|
enableMetrics: boolean; |
||||
|
enableHealthChecks: boolean; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Configuration loader class |
||||
|
*/ |
||||
|
export class ConfigLoader { |
||||
|
private static instance: ConfigLoader; |
||||
|
private config: TimeSafariConfig | null = null; |
||||
|
|
||||
|
private constructor() {} |
||||
|
|
||||
|
/** |
||||
|
* Get singleton instance |
||||
|
*/ |
||||
|
public static getInstance(): ConfigLoader { |
||||
|
if (!ConfigLoader.instance) { |
||||
|
ConfigLoader.instance = new ConfigLoader(); |
||||
|
} |
||||
|
return ConfigLoader.instance; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Load configuration from JSON file |
||||
|
*/ |
||||
|
public async loadConfig(): Promise<TimeSafariConfig> { |
||||
|
if (this.config) { |
||||
|
return this.config; |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
// In a real app, this would fetch from a config file
|
||||
|
// For test apps, we'll use a hardcoded config
|
||||
|
this.config = { |
||||
|
timesafari: { |
||||
|
appId: "app.timesafari.test", |
||||
|
appName: "TimeSafari Test", |
||||
|
version: "1.0.0", |
||||
|
description: "Test app for TimeSafari Daily Notification Plugin integration" |
||||
|
}, |
||||
|
endorser: { |
||||
|
baseUrl: "http://localhost:3001", |
||||
|
apiVersion: "v2", |
||||
|
endpoints: { |
||||
|
offers: "/api/v2/report/offers", |
||||
|
offersToPlans: "/api/v2/report/offersToPlansOwnedByMe", |
||||
|
plansLastUpdated: "/api/v2/report/plansLastUpdatedBetween", |
||||
|
notificationsBundle: "/api/v2/report/notifications/bundle" |
||||
|
}, |
||||
|
authentication: { |
||||
|
type: "Bearer", |
||||
|
token: "test-jwt-token-12345", |
||||
|
headers: { |
||||
|
"Authorization": "Bearer test-jwt-token-12345", |
||||
|
"Content-Type": "application/json", |
||||
|
"X-Privacy-Level": "user-controlled" |
||||
|
} |
||||
|
}, |
||||
|
pagination: { |
||||
|
defaultLimit: 50, |
||||
|
maxLimit: 100, |
||||
|
hitLimitThreshold: 50 |
||||
|
} |
||||
|
}, |
||||
|
notificationTypes: { |
||||
|
offers: { |
||||
|
enabled: true, |
||||
|
types: [ |
||||
|
"new_to_me", |
||||
|
"changed_to_me", |
||||
|
"new_to_projects", |
||||
|
"changed_to_projects", |
||||
|
"new_to_favorites", |
||||
|
"changed_to_favorites" |
||||
|
] |
||||
|
}, |
||||
|
projects: { |
||||
|
enabled: true, |
||||
|
types: [ |
||||
|
"local_new", |
||||
|
"local_changed", |
||||
|
"content_interest_new", |
||||
|
"favorited_changed" |
||||
|
] |
||||
|
}, |
||||
|
people: { |
||||
|
enabled: true, |
||||
|
types: [ |
||||
|
"local_new", |
||||
|
"local_changed", |
||||
|
"content_interest_new", |
||||
|
"favorited_changed", |
||||
|
"contacts_changed" |
||||
|
] |
||||
|
}, |
||||
|
items: { |
||||
|
enabled: true, |
||||
|
types: [ |
||||
|
"local_new", |
||||
|
"local_changed", |
||||
|
"favorited_changed" |
||||
|
] |
||||
|
} |
||||
|
}, |
||||
|
scheduling: { |
||||
|
contentFetch: { |
||||
|
schedule: "0 8 * * *", |
||||
|
time: "08:00", |
||||
|
description: "8 AM daily - fetch community updates" |
||||
|
}, |
||||
|
userNotification: { |
||||
|
schedule: "0 9 * * *", |
||||
|
time: "09:00", |
||||
|
description: "9 AM daily - notify users of community updates" |
||||
|
} |
||||
|
}, |
||||
|
testData: { |
||||
|
userDid: "did:example:testuser123", |
||||
|
starredPlanIds: [ |
||||
|
"plan-community-garden", |
||||
|
"plan-local-food", |
||||
|
"plan-sustainability" |
||||
|
], |
||||
|
lastKnownOfferId: "01HSE3R9MAC0FT3P3KZ382TWV7", |
||||
|
lastKnownPlanId: "01HSE3R9MAC0FT3P3KZ382TWV8", |
||||
|
mockOffers: [ |
||||
|
{ |
||||
|
jwtId: "01HSE3R9MAC0FT3P3KZ382TWV7", |
||||
|
handleId: "offer-web-dev-001", |
||||
|
offeredByDid: "did:example:offerer123", |
||||
|
recipientDid: "did:example:testuser123", |
||||
|
objectDescription: "Web development services for community project", |
||||
|
unit: "USD", |
||||
|
amount: 1000, |
||||
|
amountGiven: 500, |
||||
|
amountGivenConfirmed: 250 |
||||
|
} |
||||
|
], |
||||
|
mockProjects: [ |
||||
|
{ |
||||
|
plan: { |
||||
|
jwtId: "01HSE3R9MAC0FT3P3KZ382TWV8", |
||||
|
handleId: "plan-community-garden", |
||||
|
name: "Community Garden Project", |
||||
|
description: "Building a community garden for local food production", |
||||
|
issuerDid: "did:example:issuer123", |
||||
|
agentDid: "did:example:agent123" |
||||
|
}, |
||||
|
wrappedClaimBefore: null |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
callbacks: { |
||||
|
offers: { |
||||
|
enabled: true, |
||||
|
localHandler: "handleOffersNotification" |
||||
|
}, |
||||
|
projects: { |
||||
|
enabled: true, |
||||
|
localHandler: "handleProjectsNotification" |
||||
|
}, |
||||
|
people: { |
||||
|
enabled: true, |
||||
|
localHandler: "handlePeopleNotification" |
||||
|
}, |
||||
|
items: { |
||||
|
enabled: true, |
||||
|
localHandler: "handleItemsNotification" |
||||
|
}, |
||||
|
communityAnalytics: { |
||||
|
enabled: true, |
||||
|
endpoint: "http://localhost:3001/api/analytics/community-events", |
||||
|
headers: { |
||||
|
"Content-Type": "application/json", |
||||
|
"X-Privacy-Level": "aggregated" |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
observability: { |
||||
|
enableLogging: true, |
||||
|
logLevel: "debug", |
||||
|
enableMetrics: true, |
||||
|
enableHealthChecks: true |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
return this.config; |
||||
|
} catch (error) { |
||||
|
console.error('Failed to load configuration:', error); |
||||
|
throw error; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get configuration |
||||
|
*/ |
||||
|
public getConfig(): TimeSafariConfig { |
||||
|
if (!this.config) { |
||||
|
throw new Error('Configuration not loaded. Call loadConfig() first.'); |
||||
|
} |
||||
|
return this.config; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get Endorser.ch API URL for a specific endpoint |
||||
|
*/ |
||||
|
public getEndorserUrl(endpoint: keyof TimeSafariConfig['endorser']['endpoints']): string { |
||||
|
const config = this.getConfig(); |
||||
|
return `${config.endorser.baseUrl}${config.endorser.endpoints[endpoint]}`; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get authentication headers |
||||
|
*/ |
||||
|
public getAuthHeaders(): Record<string, string> { |
||||
|
const config = this.getConfig(); |
||||
|
return config.endorser.authentication.headers; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get test data |
||||
|
*/ |
||||
|
public getTestData() { |
||||
|
const config = this.getConfig(); |
||||
|
return config.testData; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get notification types for a specific category |
||||
|
*/ |
||||
|
public getNotificationTypes(category: keyof TimeSafariConfig['notificationTypes']) { |
||||
|
const config = this.getConfig(); |
||||
|
return config.notificationTypes[category]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Logger utility for test apps |
||||
|
*/ |
||||
|
export class TestLogger { |
||||
|
private logLevel: string; |
||||
|
|
||||
|
constructor(logLevel: string = 'debug') { |
||||
|
this.logLevel = logLevel; |
||||
|
} |
||||
|
|
||||
|
private shouldLog(level: string): boolean { |
||||
|
const levels = ['error', 'warn', 'info', 'debug']; |
||||
|
return levels.indexOf(level) <= levels.indexOf(this.logLevel); |
||||
|
} |
||||
|
|
||||
|
public debug(message: string, data?: any) { |
||||
|
if (this.shouldLog('debug')) { |
||||
|
console.log(`[DEBUG] ${message}`, data || ''); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public info(message: string, data?: any) { |
||||
|
if (this.shouldLog('info')) { |
||||
|
console.log(`[INFO] ${message}`, data || ''); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public warn(message: string, data?: any) { |
||||
|
if (this.shouldLog('warn')) { |
||||
|
console.warn(`[WARN] ${message}`, data || ''); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public error(message: string, data?: any) { |
||||
|
if (this.shouldLog('error')) { |
||||
|
console.error(`[ERROR] ${message}`, data || ''); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Mock DailyNotificationService for test apps |
||||
|
*/ |
||||
|
export class MockDailyNotificationService { |
||||
|
private config: TimeSafariConfig; |
||||
|
private logger: TestLogger; |
||||
|
private isInitialized = false; |
||||
|
|
||||
|
constructor(config: TimeSafariConfig) { |
||||
|
this.config = config; |
||||
|
this.logger = new TestLogger(config.observability.logLevel); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Initialize the service |
||||
|
*/ |
||||
|
public async initialize(): Promise<void> { |
||||
|
this.logger.info('Initializing Mock DailyNotificationService'); |
||||
|
this.isInitialized = true; |
||||
|
this.logger.info('Mock DailyNotificationService initialized successfully'); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Schedule dual notification (content fetch + user notification) |
||||
|
*/ |
||||
|
public async scheduleDualNotification(config: any): Promise<void> { |
||||
|
if (!this.isInitialized) { |
||||
|
throw new Error('Service not initialized'); |
||||
|
} |
||||
|
|
||||
|
this.logger.info('Scheduling dual notification', config); |
||||
|
|
||||
|
// Simulate content fetch
|
||||
|
if (config.contentFetch?.enabled) { |
||||
|
await this.simulateContentFetch(config.contentFetch); |
||||
|
} |
||||
|
|
||||
|
// Simulate user notification
|
||||
|
if (config.userNotification?.enabled) { |
||||
|
await this.simulateUserNotification(config.userNotification); |
||||
|
} |
||||
|
|
||||
|
this.logger.info('Dual notification scheduled successfully'); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Register callback |
||||
|
*/ |
||||
|
public async registerCallback(name: string, callback: Function): Promise<void> { |
||||
|
this.logger.info(`Registering callback: ${name}`); |
||||
|
// In a real implementation, this would register the callback
|
||||
|
this.logger.info(`Callback ${name} registered successfully`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get dual schedule status |
||||
|
*/ |
||||
|
public async getDualScheduleStatus(): Promise<any> { |
||||
|
return { |
||||
|
contentFetch: { |
||||
|
enabled: true, |
||||
|
lastFetch: new Date().toISOString(), |
||||
|
nextFetch: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString() |
||||
|
}, |
||||
|
userNotification: { |
||||
|
enabled: true, |
||||
|
lastNotification: new Date().toISOString(), |
||||
|
nextNotification: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString() |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Cancel all notifications |
||||
|
*/ |
||||
|
public async cancelAllNotifications(): Promise<void> { |
||||
|
this.logger.info('Cancelling all notifications'); |
||||
|
this.logger.info('All notifications cancelled successfully'); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Simulate content fetch using Endorser.ch API patterns |
||||
|
*/ |
||||
|
private async simulateContentFetch(config: any): Promise<void> { |
||||
|
this.logger.info('Simulating content fetch from Endorser.ch API'); |
||||
|
|
||||
|
try { |
||||
|
// Simulate parallel API requests
|
||||
|
const testData = this.config.testData; |
||||
|
|
||||
|
// Mock offers to person
|
||||
|
const offersToPerson = { |
||||
|
data: testData.mockOffers, |
||||
|
hitLimit: false |
||||
|
}; |
||||
|
|
||||
|
// Mock offers to projects
|
||||
|
const offersToProjects = { |
||||
|
data: [], |
||||
|
hitLimit: false |
||||
|
}; |
||||
|
|
||||
|
// Mock starred project changes
|
||||
|
const starredChanges = { |
||||
|
data: testData.mockProjects, |
||||
|
hitLimit: false |
||||
|
}; |
||||
|
|
||||
|
this.logger.info('Content fetch simulation completed', { |
||||
|
offersToPerson: offersToPerson.data.length, |
||||
|
offersToProjects: offersToProjects.data.length, |
||||
|
starredChanges: starredChanges.data.length |
||||
|
}); |
||||
|
|
||||
|
// Call success callback if provided
|
||||
|
if (config.callbacks?.onSuccess) { |
||||
|
await config.callbacks.onSuccess({ |
||||
|
offersToPerson, |
||||
|
offersToProjects, |
||||
|
starredChanges |
||||
|
}); |
||||
|
} |
||||
|
} catch (error) { |
||||
|
this.logger.error('Content fetch simulation failed', error); |
||||
|
if (config.callbacks?.onError) { |
||||
|
await config.callbacks.onError(error); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Simulate user notification |
||||
|
*/ |
||||
|
private async simulateUserNotification(config: any): Promise<void> { |
||||
|
this.logger.info('Simulating user notification', { |
||||
|
title: config.title, |
||||
|
body: config.body, |
||||
|
time: config.schedule |
||||
|
}); |
||||
|
this.logger.info('User notification simulation completed'); |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue