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.
 
 
 
 
 
 

550 lines
14 KiB

/**
* 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;
private logs: 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);
}
private addToLogs(level: string, message: string, data?: any): void {
const timestamp = new Date().toISOString();
const logEntry = `[${timestamp}] [${level.toUpperCase()}] ${message}`;
this.logs.push(logEntry);
// Keep only last 1000 logs to prevent memory issues
if (this.logs.length > 1000) {
this.logs = this.logs.slice(-1000);
}
}
public debug(message: string, data?: any) {
if (this.shouldLog('debug')) {
console.log(`[DEBUG] ${message}`, data || '');
this.addToLogs('debug', message, data);
}
}
public info(message: string, data?: any) {
if (this.shouldLog('info')) {
console.log(`[INFO] ${message}`, data || '');
this.addToLogs('info', message, data);
}
}
public warn(message: string, data?: any) {
if (this.shouldLog('warn')) {
console.warn(`[WARN] ${message}`, data || '');
this.addToLogs('warn', message, data);
}
}
public error(message: string, data?: any) {
if (this.shouldLog('error')) {
console.error(`[ERROR] ${message}`, data || '');
this.addToLogs('error', message, data);
}
}
public getLogs(): string[] {
return [...this.logs];
}
public clearLogs(): void {
this.logs = [];
}
public exportLogs(): string {
return this.logs.join('\n');
}
}
/**
* 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');
}
}