// Generated, do not edit. sha256:ddd8b3b7b2f9aadd1a75eeaa934fd9a7d05a36cbd2f06e3a6cc5379098c70228 import * as db from '../db.js'; import { toSearchName } from '../../shared/search-name.js'; import { ENUMS, CONNECTION_PROVIDER } from '../../shared/config.js'; import { assertEnum, nowIso } from './_shared.js'; import { emitProvidersChanged } from './providers.js'; import { emitChange } from './_events.js'; import { trackUpdate, trackUpdateWhere, trackUpdateField, trackDelete } from './change-log.js'; import { fail } from '../../shared/app-error.js'; import { hasFeature } from '../license.mjs'; function emitConnectionsChanged() { emitChange('connections-changed'); } function syncConnectionProvider(conn) { const providerKey = CONNECTION_PROVIDER[conn.kind]; if (!providerKey) return; const p = db.one(`SELECT id, baseUrl FROM Provider WHERE key = ?`, [providerKey]); if (!p || p.baseUrl === conn.url) return; db.run(`UPDATE Provider SET baseUrl = ? WHERE id = ?`, [conn.url, p.id]); emitProvidersChanged(); } export function listConnections(kind = null) { if (kind != null) assertEnum('kind', ENUMS.CONNECTION_KIND, kind); return kind ? db.all(`SELECT * FROM Connection WHERE kind = ? ORDER BY isSystem DESC, name`, [kind]) : db.all(`SELECT * FROM Connection ORDER BY kind, isSystem DESC, name`); } export function activeConnection(kind) { assertEnum('kind', ENUMS.CONNECTION_KIND, kind); if (!CONNECTION_PROVIDER[kind] && kind !== 'btc_explorer') { throw new Error(`Kind '${kind}' is linked per account; use listConnections('${kind}').`); } const row = db.one(`SELECT * FROM Connection WHERE kind = ? AND isActive = 1`, [kind]); if (!row) fail('connections.noActive', { kind }, `No active '${kind}' connection.`); return row; } export async function saveConnection({ id = null, kind, name, url }) { assertEnum('kind', ENUMS.CONNECTION_KIND, kind); if ((kind === 'lnd' || kind === 'nwc') && !hasFeature('lightning')) { fail('connections.lightningPaid', null, 'Lightning connections belong to the paid version.'); } if (kind === 'btc_explorer' && !hasFeature('ownMempool')) { fail('connections.ownExplorerPaid', null, 'Your own explorer belongs to the paid version; the standard one keeps working.'); } name = (name || '').trim(); url = (url || '').trim().replace(/\/+$/, ''); if (!name) fail('connections.nameRequired', null, 'A name is required.'); if (!/^(https?|wss?):\/\//.test(url)) fail('connections.urlScheme', null, 'A URL starts with http(s):// or ws(s)://.'); const now = nowIso(); if (id == null) { if (db.one(`SELECT 1 AS x FROM Connection WHERE kind = ? AND name = ?`, [kind, name])) { fail('connections.nameInUse', { kind, naam: name }, `A '${kind}' connection named '${name}' already exists.`); } db.run(`INSERT INTO Connection (name, searchName, url, kind, isActive, isSystem, lastStatus, lastCheckedAt, createdAt, updatedAt) VALUES (?,?,?,?,0,0,NULL,NULL,?,?)`, [name, toSearchName(name), url, kind, now, now]); id = db.lastId(); } else { const cur = db.one(`SELECT * FROM Connection WHERE id = ?`, [id]); if (!cur) fail('connections.unknownConnection', { id }, `Connection #${id} does not exist.`); if (cur.isSystem) throw new Error('This profile is a system row (the guaranteed fallback) and cannot be edited.'); if (cur.kind !== kind) throw new Error('The kind of an existing connection cannot be changed.'); const urlChanged = cur.url !== url; trackUpdate('Connection', id, () => db.run(`UPDATE Connection SET name = ?, searchName = ?, url = ?, lastStatus = ${urlChanged ? 'NULL' : 'lastStatus'}, lastCheckedAt = ${urlChanged ? 'NULL' : 'lastCheckedAt'}, updatedAt = ? WHERE id = ?`, [name, toSearchName(name), url, now, id])); if (urlChanged && cur.isActive) syncConnectionProvider({ kind: cur.kind, url }); } await db.persist(); emitConnectionsChanged(); return id; } export async function setActiveConnection(id) { const c = db.one(`SELECT * FROM Connection WHERE id = ?`, [id]); if (!c) fail('connections.unknownConnection', { id }, `Connection #${id} does not exist.`); const now = nowIso(); if (c.kind === 'btc_explorer') { trackUpdateWhere('Connection', `kind = ? AND isActive = 1 AND id != ?`, [c.kind, id], () => db.run(`UPDATE Connection SET isActive = 0, updatedAt = ? WHERE kind = ? AND isActive = 1 AND id != ?`, [now, c.kind, id])); } trackUpdate('Connection', id, () => db.run(`UPDATE Connection SET isActive = 1, updatedAt = ? WHERE id = ?`, [now, id])); if (c.kind === 'btc_explorer') syncConnectionProvider({ kind: c.kind, url: c.url }); await db.persist(); emitConnectionsChanged(); } export async function deleteConnection(id) { const c = db.one(`SELECT * FROM Connection WHERE id = ?`, [id]); if (!c) fail('connections.unknownConnection', { id }, `Connection #${id} does not exist.`); if (c.isSystem) throw new Error('This profile is a system row (the guaranteed fallback) and cannot be deleted.'); const linked = db.one(`SELECT COUNT(*) AS n FROM WalletSource WHERE connectionId = ?`, [id]); if (linked && linked.n > 0) { fail('connections.linkedToSources', { n: linked.n }, `This connection is still attached to ${linked.n} tracking source(s).`); } if (c.kind === 'btc_explorer' && c.isActive) { const fallback = db.one(`SELECT * FROM Connection WHERE kind = 'btc_explorer' AND isSystem = 1`); if (!fallback) throw new Error('No system fallback profile found; refusing to delete.'); const now = nowIso(); trackUpdate('Connection', c.id, () => db.run(`UPDATE Connection SET isActive = 0, updatedAt = ? WHERE id = ?`, [now, c.id])); trackUpdate('Connection', fallback.id, () => db.run(`UPDATE Connection SET isActive = 1, updatedAt = ? WHERE id = ?`, [now, fallback.id])); syncConnectionProvider({ kind: 'btc_explorer', url: fallback.url }); } trackDelete('Connection', id, () => db.run(`DELETE FROM Connection WHERE id = ?`, [id])); await db.persist(); emitConnectionsChanged(); } export async function recordConnectionStatus(id, ok) { const now = nowIso(); trackUpdateField('Connection', id, 'lastStatus', () => db.run(`UPDATE Connection SET lastStatus = ?, lastCheckedAt = ?, updatedAt = ? WHERE id = ?`, [ok ? 'ok' : 'fail', now, now, id])); await db.persist(); emitConnectionsChanged(); }