From fc7113be4b016ed05f04733cfcc2c319261c122c Mon Sep 17 00:00:00 2001 From: redshift <213178690+1ftredsh@users.noreply.github.com> Date: Tue, 28 Apr 2026 15:22:21 +0530 Subject: [PATCH] refactor: read clients list through abstraction for local and remote mode Replace direct store.getState().clientIds access with a shared utility: - getClientsFromStore(store) for daemon/local usage - getClientsList() for remote CLI usage via daemon API This ensures integration refresh logic and client management work consistently whether running inside the daemon or connecting to a remote daemon where store access is unavailable. Updated: - src/daemon/index.ts: use getClientsFromStore for integration refresh - src/daemon/http/index.ts: use getClientsFromStore in /clients endpoints - src/utils/daemon-client.ts: use getClientsList in ensureDaemonClient - src/cli.ts: use getClientsList in clients list command --- src/cli.ts | 28 ++++++---------- src/daemon/http/index.ts | 46 +++++++++----------------- src/daemon/index.ts | 7 ++-- src/utils/clients.ts | 66 ++++++++++++++++++++++++++++++++++++++ src/utils/daemon-client.ts | 18 +++++++---- 5 files changed, 105 insertions(+), 60 deletions(-) create mode 100644 src/utils/clients.ts diff --git a/src/cli.ts b/src/cli.ts index cf14187..2bc8406 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -11,6 +11,7 @@ import { addDaemonClient, ensureDaemonClient, } from "./utils/daemon-client"; +import { getClientsList } from "./utils/clients"; import { existsSync, mkdirSync } from "fs"; import { execSync } from "child_process"; import { @@ -731,26 +732,15 @@ clientsCmd const config = await loadConfig(); const suffix = getNpubSuffix(config); - const result = await callDaemon("/clients"); - if (result.error) { - console.log(result.error); - process.exit(1); - } + const entries = await getClientsList(); - const output = result.output as - | { - clients: Array<{ - id: string; - name: string; - apiKey: string; - createdAt: number; - lastUsed?: number | null; - }>; - totalCount: number; - } - | undefined; - - let clients = output?.clients || []; + let clients = entries.map((c) => ({ + id: c.clientId, + name: c.name, + apiKey: c.apiKey, + createdAt: c.createdAt, + lastUsed: c.lastUsed, + })); if (suffix) { const suffixStr = `_${suffix}`; diff --git a/src/daemon/http/index.ts b/src/daemon/http/index.ts index 767dc09..f567e6e 100644 --- a/src/daemon/http/index.ts +++ b/src/daemon/http/index.ts @@ -14,6 +14,7 @@ import { type CocodState, } from "../wallet/cocod-client"; import { decodeCashuTokenAmount } from "../wallet"; +import { getClientsFromStore } from "../../utils/clients"; type ClientMode = "xcashu" | "lazyrefund" | "apikeys"; @@ -63,12 +64,8 @@ function getClientIdFromRequest( return undefined; } - const state = store.getState(); - const clientIds = state.clientIds || []; - - const matchingClient = ( - clientIds as { clientId: string; apiKey: string }[] - ).find((c) => c.apiKey === apiKey); + const clients = getClientsFromStore(store); + const matchingClient = clients.find((c) => c.apiKey === apiKey); return matchingClient?.clientId; } @@ -710,24 +707,13 @@ export function createDaemonRequestHandler(deps: { // Client management endpoints if (req.method === "GET" && url.pathname === "/clients") { try { - const state = deps.store.getState(); - const clientIds = state.clientIds || []; - - const clients = clientIds.map( - (c: { - clientId: string; - name: string; - apiKey: string; - createdAt: number; - lastUsed?: number | null; - }) => ({ - id: c.clientId, - name: c.name, - apiKey: c.apiKey, - createdAt: c.createdAt, - lastUsed: c.lastUsed, - }), - ); + const clients = getClientsFromStore(deps.store).map((c) => ({ + id: c.clientId, + name: c.name, + apiKey: c.apiKey, + createdAt: c.createdAt, + lastUsed: c.lastUsed, + })); res.writeHead(200, { "Content-Type": "application/json" }); res.end( @@ -793,10 +779,9 @@ export function createDaemonRequestHandler(deps: { return; } - const state = deps.store.getState(); - const existingClients = state.clientIds || []; + const existingClients = getClientsFromStore(deps.store); const existingClient = existingClients.find( - (c: { clientId: string }) => c.clientId === clientId, + (c) => c.clientId === clientId, ); if (existingClient) { @@ -863,10 +848,9 @@ export function createDaemonRequestHandler(deps: { return; } - const state = deps.store.getState(); - const existingClients = state.clientIds || []; + const existingClients = getClientsFromStore(deps.store); const index = existingClients.findIndex( - (c: { clientId: string }) => c.clientId === id, + (c) => c.clientId === id, ); if (index === -1) { @@ -879,7 +863,7 @@ export function createDaemonRequestHandler(deps: { return; } - const removedClient = existingClients[index]; + const removedClient = existingClients[index]!; const updatedClients = existingClients.filter( (_c: unknown, i: number) => i !== index, ); diff --git a/src/daemon/index.ts b/src/daemon/index.ts index df2de49..2a033a2 100644 --- a/src/daemon/index.ts +++ b/src/daemon/index.ts @@ -21,6 +21,7 @@ import { createCocodClient } from "./wallet/cocod-client"; import { createModelService } from "./models"; import { createDaemonRequestHandler } from "./http"; import { runIntegrationsForClients } from "../integrations"; +import { getClientsFromStore } from "../utils/clients"; import { RoutstrClient } from "@routstr/sdk"; async function main(): Promise { @@ -108,8 +109,7 @@ async function main(): Promise { logger.log("Scheduled model refresh completed successfully."); // Refresh integrations for all registered clients - const state = store.getState(); - const clientIds = state.clientIds || []; + const clientIds = getClientsFromStore(store); if (clientIds.length > 0) { logger.log(`Refreshing ${clientIds.length} client integration(s)...`); await runIntegrationsForClients(clientIds, updatedConfig); @@ -217,8 +217,7 @@ async function main(): Promise { .then(async () => { logger.log("Initial model refresh completed."); // Refresh integrations for all registered clients after initial bootstrap - const state = store.getState(); - const clientIds = state.clientIds || []; + const clientIds = getClientsFromStore(store); if (clientIds.length > 0) { logger.log(`Refreshing ${clientIds.length} client integration(s)...`); await runIntegrationsForClients(clientIds, updatedConfig); diff --git a/src/utils/clients.ts b/src/utils/clients.ts new file mode 100644 index 0000000..0e9f248 --- /dev/null +++ b/src/utils/clients.ts @@ -0,0 +1,66 @@ +import { callDaemon } from "./daemon-client"; + +export interface ClientEntry { + clientId: string; + name: string; + apiKey: string; + createdAt: number; + lastUsed?: number | null; +} + +/** + * Read the clients list directly from the SDK store. + * Use this when running inside the daemon (local mode). + */ +export function getClientsFromStore(store: { getState(): any }): ClientEntry[] { + const state = store.getState(); + const clientIds = state.clientIds || []; + return clientIds.map( + (c: { + clientId: string; + name: string; + apiKey: string; + createdAt: number; + lastUsed?: number | null; + }) => ({ + clientId: c.clientId, + name: c.name, + apiKey: c.apiKey, + createdAt: c.createdAt, + lastUsed: c.lastUsed, + }), + ); +} + +/** + * Fetch the clients list from the daemon API. + * Use this when running remotely (CLI in remote mode). + */ +export async function getClientsList(): Promise { + const result = await callDaemon("/clients"); + const clients = ( + result.output as + | { + clients?: Array<{ + id: string; + name: string; + apiKey: string; + createdAt: number; + lastUsed?: number | null; + }>; + } + | undefined + )?.clients; + + if (!clients) { + return []; + } + + return clients.map((c) => ({ + clientId: c.id, + name: c.name, + apiKey: c.apiKey, + createdAt: c.createdAt, + lastUsed: c.lastUsed, + })); +} diff --git a/src/utils/daemon-client.ts b/src/utils/daemon-client.ts index c492938..0f20561 100644 --- a/src/utils/daemon-client.ts +++ b/src/utils/daemon-client.ts @@ -11,6 +11,7 @@ import { npubFromSecretKey, type HttpMethod, } from "./nip98"; +import { getClientsList } from "./clients"; export interface CommandResponse { output?: unknown; @@ -57,18 +58,23 @@ export async function ensureDaemonClient( throw error; } - const clientsResult = await callDaemon("/clients"); - const clients = - (clientsResult.output as { clients?: DaemonClient[] } | undefined) - ?.clients || []; - const client = clients.find((c) => c.id === clientId); + const clients = await getClientsList(); + const entry = clients.find((c) => c.clientId === clientId); - if (!client?.apiKey) { + if (!entry?.apiKey) { throw new Error( `Client '${clientId}' already exists but could not be fetched from the daemon.`, ); } + const client: DaemonClient = { + id: entry.clientId, + name: entry.name, + apiKey: entry.apiKey, + createdAt: entry.createdAt, + lastUsed: entry.lastUsed, + }; + return { client, created: false }; } }