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
This commit is contained in:
redshift
2026-04-28 15:22:21 +05:30
parent 711a04f09d
commit fc7113be4b
5 changed files with 105 additions and 60 deletions
+9 -19
View File
@@ -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}`;
+15 -31
View File
@@ -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,
);
+3 -4
View File
@@ -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<void> {
@@ -108,8 +109,7 @@ async function main(): Promise<void> {
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<void> {
.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);
+66
View File
@@ -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<ClientEntry[]> {
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,
}));
}
+12 -6
View File
@@ -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 };
}
}