import path from 'node:path' import type { JsonObject } from '../../types/json.js' import { APP_CONFIG_KEYS } from '../../config/app-config-keys.js' import { PROJECT_ROOT } from '../../config/runtime.js' import { readAppConfigEntry, saveAppConfigEntry } from '../config/app-config-store.js' const NOTIFICATION_CONFIG_FILE_PATH = path.join(PROJECT_ROOT, 'data', 'notification-config.json') const DEFAULT_BARK_SERVER_URL = 'https://api.day.app' type NotificationRecipient = { id: string name: string deviceKey?: string apiKey?: string enabled: boolean } export function getNotificationConfigFilePath() { return NOTIFICATION_CONFIG_FILE_PATH } export function getNotificationConfig() { return readAppConfigEntry({ configKey: APP_CONFIG_KEYS.notification, legacyFilePath: NOTIFICATION_CONFIG_FILE_PATH, fallback: createDefaultNotificationConfig, normalize: normalizeNotificationConfig, }) } export function saveNotificationConfig(rawValue: unknown) { return saveAppConfigEntry({ configKey: APP_CONFIG_KEYS.notification, legacyFilePath: NOTIFICATION_CONFIG_FILE_PATH, value: rawValue, normalize: normalizeNotificationConfig, }) } export function listEnabledBarkRecipients(config: JsonObject = getNotificationConfig()) { if (config.enabled === false || config.channels?.bark?.enabled === false) { return [] } return ( Array.isArray(config.channels?.bark?.recipients) ? config.channels.bark.recipients : [] ).filter((item: JsonObject) => item.enabled !== false && String(item.deviceKey || '').trim()) } export function listEnabledWpushRecipients(config: JsonObject = getNotificationConfig()) { if (config.enabled === false || config.channels?.wpush?.enabled === false) { return [] } return ( Array.isArray(config.channels?.wpush?.recipients) ? config.channels.wpush.recipients : [] ).filter( (item: JsonObject) => item.enabled !== false && String(item.apiKey || item.apikey || '').trim(), ) } export function normalizeNotificationConfig(rawValue: unknown) { const source = isPlainObject(rawValue) ? rawValue : {} const bark = isPlainObject(source.channels?.bark) ? source.channels.bark : {} const wpush = isPlainObject(source.channels?.wpush) ? source.channels.wpush : {} return { enabled: typeof source.enabled === 'boolean' ? source.enabled : true, channels: { bark: { enabled: typeof bark.enabled === 'boolean' ? bark.enabled : true, serverUrl: normalizeBarkServerUrl(bark.serverUrl), recipients: (Array.isArray(bark.recipients) ? bark.recipients : []) .map((item: unknown) => normalizeBarkRecipient(item)) .filter(Boolean), }, wpush: { enabled: typeof wpush.enabled === 'boolean' ? wpush.enabled : true, recipients: (Array.isArray(wpush.recipients) ? wpush.recipients : []) .map((item: unknown) => normalizeWpushRecipient(item)) .filter(Boolean), }, }, } } function normalizeBarkRecipient(rawValue: unknown): NotificationRecipient | null { if (!isPlainObject(rawValue)) { return null } const name = String(rawValue.name || '').trim() const deviceKey = String(rawValue.deviceKey || '').trim() const id = String(rawValue.id || deviceKey || name || '').trim() if (!name && !deviceKey) { return null } return { id, name, deviceKey, enabled: typeof rawValue.enabled === 'boolean' ? rawValue.enabled : true, } } function normalizeBarkServerUrl(value: unknown) { const normalized = String(value || DEFAULT_BARK_SERVER_URL).trim() || DEFAULT_BARK_SERVER_URL return normalized.replace(/\/+$/, '') } function normalizeWpushRecipient(rawValue: unknown): NotificationRecipient | null { if (!isPlainObject(rawValue)) { return null } const name = String(rawValue.name || '').trim() const apiKey = String(rawValue.apiKey || rawValue.apikey || '').trim() const id = String(rawValue.id || apiKey || name || '').trim() if (!name && !apiKey) { return null } return { id, name, apiKey, enabled: typeof rawValue.enabled === 'boolean' ? rawValue.enabled : true, } } export function createDefaultNotificationConfig() { return { enabled: true, channels: { bark: { enabled: true, serverUrl: DEFAULT_BARK_SERVER_URL, recipients: [] as NotificationRecipient[], }, wpush: { enabled: true, recipients: [] as NotificationRecipient[], }, }, } } function isPlainObject(value: unknown): value is JsonObject { return Object.prototype.toString.call(value) === '[object Object]' }