settings.jsjavascript
/**
* @file Persistent settings store. All user-configurable settings (mirrors
* src/partials/settings.html) persist to a single settings.json in the OS app-data
* directory: ~/Library/Application Support/<AppName>/ on macOS, %APPDATA%\<AppName>\ on
* Windows, ~/.config/<AppName>/ on Linux. app.getPath('userData') resolves the correct
* per-OS, per-app location, so neither a path nor the app name is ever hardcoded. The
* file lives OUTSIDE the source tree, so it survives app updates and isn't synced by
* Dropbox/iCloud alongside the code.
*
* What does NOT belong here: OAuth tokens / API keys / passwords. Plaintext secrets must
* never sit in this JSON — those go through Electron `safeStorage` (backed by the macOS
* Keychain / Windows Credential Manager) in a separate module. The `integrations` block
* stores only non-secret connection metadata (a connected flag + display name) so the UI
* can render state without holding the secret.
*
* The default schema, deep-merge rules, and never-throw read / atomic write live in
* ./settings-store (pure Node); this file binds them to the Electron userData path and
* keeps the in-memory cache.
* @module settings
*/
const { app } = require('electron');
const path = require('path');
const store = require('./settings-store');
const DEFAULTS = store.DEFAULTS;
/**
* Loaded settings, kept in memory after first load. Null until then.
* @type {?Object}
*/
let cache = null;
/**
* Absolute path of settings.json in the Electron userData directory.
* @returns {string} File path.
*/
function filePath() {
return path.join(app.getPath('userData'), 'settings.json');
}
/**
* Read settings from disk once, then serve from the in-memory cache. A missing or
* corrupt file falls back to the defaults — the underlying read never throws.
* @returns {Object} The full settings object.
*/
function load() {
if (cache) return cache;
cache = store.read(filePath());
return cache;
}
/**
* Merge a partial patch into the current settings and persist the result. The renderer
* sends only the slice it changed (e.g. { files: { nameAffix: {...} } }); mergeDefaults
* folds it onto the cached object so untouched branches are preserved.
* @param {Object} patch Partial settings object — only the branches to change.
* @returns {Object} The full merged settings, so the caller can refresh its view in one
* round trip.
*/
function update(patch) {
const current = load();
cache = store.mergeDefaults(current, patch);
store.persist(filePath(), cache);
return cache;
}
module.exports = { load, update, filePath, DEFAULTS };