more code cleanup

This commit is contained in:
Egor 2026-01-18 14:29:56 -08:00
parent 4d515f91ea
commit 8d3804593c
4 changed files with 99 additions and 76 deletions

View file

@ -16,8 +16,8 @@ import { withAuth, withFormAuth } from './utils/auth';
let daemon: ReturnType<typeof Bun.spawn> | null = null; let daemon: ReturnType<typeof Bun.spawn> | null = null;
try {
daemon = await startDaemon(); daemon = await startDaemon();
const isLinked = await checkSignalCli(); const isLinked = await checkSignalCli();
const hasAccount = isLinked && (await initSignal({})); const hasAccount = isLinked && (await initSignal({}));
@ -27,6 +27,10 @@ if (hasAccount) {
console.log(chalk.yellow('⚠ No Signal account linked')); console.log(chalk.yellow('⚠ No Signal account linked'));
console.log(chalk.dim(` Visit http://localhost:${PORT}/link to link your device`)); console.log(chalk.dim(` Visit http://localhost:${PORT}/link to link your device`));
} }
} catch (error) {
console.error(chalk.red('✗ Failed to start signal-cli daemon'));
console.error(chalk.dim(` ${error instanceof Error ? error.message : String(error)}`));
}
if (!API_KEY) { if (!API_KEY) {
console.warn(chalk.yellow('⚠️ Server running without API_KEY')); console.warn(chalk.yellow('⚠️ Server running without API_KEY'));

View file

@ -4,13 +4,12 @@ import { DAEMON_START_MAX_ATTEMPTS, DEVICE_NAME, VERBOSE } from '../constants/co
import { SIGNAL_CLI, SIGNAL_CLI_DATA, SIGNAL_CLI_SOCKET } from '../constants/paths'; import { SIGNAL_CLI, SIGNAL_CLI_DATA, SIGNAL_CLI_SOCKET } from '../constants/paths';
import type { ListAccountsResult, StartLinkResult, UpdateGroupResult } from '../types'; import type { ListAccountsResult, StartLinkResult, UpdateGroupResult } from '../types';
import { log } from '../utils/log'; import { log } from '../utils/log';
import { call } from '../utils/rpc';
log(`Running signal-cli from ${SIGNAL_CLI}`); log(`Running signal-cli from ${SIGNAL_CLI}`);
const MESSAGE_DELIMITER = '\n';
let account: string | null = null; let account: string | null = null;
let currentLinkUri: string | null = null; let currentLinkUri: string | null = null;
let rpcId = 1;
export async function initSignal({ accountOverride }: { accountOverride?: string }) { export async function initSignal({ accountOverride }: { accountOverride?: string }) {
if (accountOverride) { if (accountOverride) {
@ -18,7 +17,7 @@ export async function initSignal({ accountOverride }: { accountOverride?: string
return true; return true;
} }
const result = (await rpcCall('listAccounts', {})) as ListAccountsResult; const result = (await call('listAccounts', {}, account)) as ListAccountsResult;
const [firstAccount] = result; const [firstAccount] = result;
if (firstAccount) { if (firstAccount) {
account = firstAccount; account = firstAccount;
@ -28,57 +27,14 @@ export async function initSignal({ accountOverride }: { accountOverride?: string
return false; return false;
} }
async function rpcCall(method: string, params: Record<string, unknown>) {
return new Promise((resolve, reject) => {
let response = '';
Bun.connect({
unix: SIGNAL_CLI_SOCKET,
socket: {
data(socket, data) {
response += new TextDecoder().decode(data);
const isComplete = response.includes(MESSAGE_DELIMITER);
if (isComplete) {
socket.end();
const parsed = JSON.parse(response.trim());
if (parsed.error) {
reject(new Error(`signal-cli RPC error: ${parsed.error.message}`));
} else {
resolve(parsed.result);
}
}
},
error(_socket, error) {
reject(error);
},
close() {
const isComplete = response.includes(MESSAGE_DELIMITER);
if (!isComplete) {
reject(new Error('Connection closed before response received'));
}
},
open(socket) {
const request = JSON.stringify({
jsonrpc: '2.0',
id: rpcId++,
method,
params: account ? { account, ...params } : params,
});
socket.write(`${request}${MESSAGE_DELIMITER}`);
},
},
});
});
}
export async function generateLinkQR() { export async function generateLinkQR() {
const result = (await rpcCall('startLink', { const result = (await call(
'startLink',
{
deviceName: DEVICE_NAME, deviceName: DEVICE_NAME,
})) as StartLinkResult; },
account,
)) as StartLinkResult;
const uri = result.deviceLinkUri; const uri = result.deviceLinkUri;
if (!uri) { if (!uri) {
@ -94,10 +50,14 @@ export async function finishLink() {
throw new Error('No link in progress'); throw new Error('No link in progress');
} }
const result = await rpcCall('finishLink', { const result = await call(
'finishLink',
{
deviceLinkUri: currentLinkUri, deviceLinkUri: currentLinkUri,
deviceName: DEVICE_NAME, deviceName: DEVICE_NAME,
}); },
account,
);
currentLinkUri = null; currentLinkUri = null;
return result; return result;
} }
@ -112,10 +72,14 @@ export async function unlinkDevice() {
} }
export async function createGroup(name: string, members: string[] = []) { export async function createGroup(name: string, members: string[] = []) {
const result = (await rpcCall('updateGroup', { const result = (await call(
'updateGroup',
{
name, name,
member: members, member: members,
})) as UpdateGroupResult; },
account,
)) as UpdateGroupResult;
if (!result?.groupId) { if (!result?.groupId) {
throw new Error('Failed to create group'); throw new Error('Failed to create group');
@ -125,15 +89,19 @@ export async function createGroup(name: string, members: string[] = []) {
} }
export async function sendGroupMessage(groupId: string, message: string) { export async function sendGroupMessage(groupId: string, message: string) {
await rpcCall('send', { await call(
'send',
{
groupId, groupId,
message, message,
}); },
account,
);
} }
export async function checkSignalCli() { export async function checkSignalCli() {
try { try {
await rpcCall('listAccounts', {}); await call('listAccounts', {}, account);
return true; return true;
} catch { } catch {
return false; return false;
@ -142,7 +110,7 @@ export async function checkSignalCli() {
export async function hasValidAccount() { export async function hasValidAccount() {
try { try {
const result = (await rpcCall('listAccounts', {})) as ListAccountsResult; const result = (await call('listAccounts', {}, account)) as ListAccountsResult;
return result.length > 0; return result.length > 0;
} catch { } catch {
return false; return false;

View file

@ -28,6 +28,7 @@ export const handleLink = async () => {
export const handleLinkQR = async () => { export const handleLinkQR = async () => {
const qrDataUrl = await generateLinkQR(); const qrDataUrl = await generateLinkQR();
return new Response(qrDataUrl, { return new Response(qrDataUrl, {
headers: { 'content-type': 'text/plain' }, headers: { 'content-type': 'text/plain' },
}); });

50
server/utils/rpc.ts Normal file
View file

@ -0,0 +1,50 @@
import { SIGNAL_CLI_SOCKET } from '../constants/paths';
const MESSAGE_DELIMITER = '\n';
let rpcId = 1;
export const call = (method: string, params: Record<string, unknown>, account: string | null) =>
new Promise((resolve, reject) => {
let response = '';
Bun.connect({
unix: SIGNAL_CLI_SOCKET,
socket: {
data(socket, data) {
response += new TextDecoder().decode(data);
const isComplete = response.includes(MESSAGE_DELIMITER);
if (isComplete) {
socket.end();
const parsed = JSON.parse(response.trim());
if (parsed.error) {
reject(new Error(`signal-cli RPC error: ${parsed.error.message}`));
} else {
resolve(parsed.result);
}
}
},
error(_socket, error) {
reject(error);
},
close() {
const isComplete = response.includes(MESSAGE_DELIMITER);
if (!isComplete) {
reject(new Error('Connection closed before response received'));
}
},
open(socket) {
const request = JSON.stringify({
jsonrpc: '2.0',
id: rpcId++,
method,
params: account ? { account, ...params } : params,
});
socket.write(`${request}${MESSAGE_DELIMITER}`);
},
},
});
});