fix(ipc-thread-handlers): drop redundant try/catch wrappers and duplicate emit

Every handler was wrapping its body in try/catch and returning fail(code,
error.message), leaking raw DB messages to the renderer and bypassing the
sanitizer in registerHandle. Removed the 14 inner try/catch shells so
errors surface through ipc-support's IPC_HANDLER_FAILED / IPC_INVALID_PAYLOAD
path. Also removed the duplicate emitEvent({ type: 'domain-changed' }) in
addThreadEvent — publishDomainChanged already emits the same event.
This commit is contained in:
ZacharyZhang-NY committed 2026-04-16 21:37:58 -04:00
1 parent 6c6cc96060
commit deb980448c
1 file changed
+139 -200
+139 -200
View File
@@ -21,249 +21,188 @@ import { fail, ok } from "./ipc-core-common";
export function registerThreadHandlers({ registerHandle, db, ensureCompanyExists, publishDomainChanged, emitEvent }: RegisterCoreHandlersDependencies) { export function registerThreadHandlers({ registerHandle, db, ensureCompanyExists, publishDomainChanged, emitEvent }: RegisterCoreHandlersDependencies) {
registerHandle(IPC_CHANNELS.createThread, async (_event, payload) => { registerHandle(IPC_CHANNELS.createThread, async (_event, payload) => {
try { const input = createThreadSchema.parse(payload);
const input = createThreadSchema.parse(payload); const companyError = ensureCompanyExists(input.companyId);
const companyError = ensureCompanyExists(input.companyId); if (companyError) return companyError;
if (companyError) return companyError; const thread = db.createThread({
const thread = db.createThread({ companyId: input.companyId,
companyId: input.companyId, title: input.title,
title: input.title, type: input.type,
type: input.type, relatedEntityType: input.relatedEntityType ?? null,
relatedEntityType: input.relatedEntityType ?? null, relatedEntityId: input.relatedEntityId ?? null,
relatedEntityId: input.relatedEntityId ?? null, });
}); publishDomainChanged();
publishDomainChanged(); return ok(thread);
return ok(thread);
} catch (error) {
return fail("THREAD_CREATE_FAILED", error instanceof Error ? error.message : "Failed to create thread");
}
}); });
registerHandle(IPC_CHANNELS.listThreads, async (_event, payload) => { registerHandle(IPC_CHANNELS.listThreads, async (_event, payload) => {
try { const input = listThreadsSchema.parse(payload);
const input = listThreadsSchema.parse(payload); const companyError = ensureCompanyExists(input.companyId);
const companyError = ensureCompanyExists(input.companyId); if (companyError) return companyError;
if (companyError) return companyError; const threads = db.listThreads({
const threads = db.listThreads({ companyId: input.companyId,
companyId: input.companyId, type: input.type,
type: input.type, relatedEntityType: input.relatedEntityType,
relatedEntityType: input.relatedEntityType, relatedEntityId: input.relatedEntityId,
relatedEntityId: input.relatedEntityId, limit: input.limit,
limit: input.limit, offset: input.offset,
offset: input.offset, });
}); return ok(threads);
return ok(threads);
} catch (error) {
return fail("THREAD_LIST_FAILED", error instanceof Error ? error.message : "Failed to list threads");
}
}); });
registerHandle(IPC_CHANNELS.getThread, async (_event, payload) => { registerHandle(IPC_CHANNELS.getThread, async (_event, payload) => {
try { const input = threadGetterSchema.parse(payload);
const input = threadGetterSchema.parse(payload); const companyError = ensureCompanyExists(input.companyId);
const companyError = ensureCompanyExists(input.companyId); if (companyError) return companyError;
if (companyError) return companyError; const thread = db.getThread(input.threadId, input.companyId);
const thread = db.getThread(input.threadId, input.companyId); if (!thread) return fail("THREAD_NOT_FOUND", "Thread not found");
if (!thread) return fail("THREAD_NOT_FOUND", "Thread not found"); return ok(thread);
return ok(thread);
} catch (error) {
return fail("THREAD_GET_FAILED", error instanceof Error ? error.message : "Failed to get thread");
}
}); });
registerHandle(IPC_CHANNELS.addThreadEvent, async (_event, payload) => { registerHandle(IPC_CHANNELS.addThreadEvent, async (_event, payload) => {
try { const input = addThreadEventSchema.parse(payload);
const input = addThreadEventSchema.parse(payload); const companyError = ensureCompanyExists(input.companyId);
const companyError = ensureCompanyExists(input.companyId); if (companyError) return companyError;
if (companyError) return companyError; const event = db.addThreadEvent({
const event = db.addThreadEvent({ threadId: input.threadId,
threadId: input.threadId, actorId: input.actorId,
actorId: input.actorId, actorType: input.actorType,
actorType: input.actorType, eventType: input.eventType,
eventType: input.eventType, body: input.body,
body: input.body, metadataJson: input.metadataJson,
metadataJson: input.metadataJson, });
}); publishDomainChanged();
publishDomainChanged(); return ok(event);
emitEvent({ type: "domain-changed" });
return ok(event);
} catch (error) {
return fail("THREAD_EVENT_ADD_FAILED", error instanceof Error ? error.message : "Failed to add thread event");
}
}); });
registerHandle(IPC_CHANNELS.listThreadEvents, async (_event, payload) => { registerHandle(IPC_CHANNELS.listThreadEvents, async (_event, payload) => {
try { const input = listThreadEventsSchema.parse(payload);
const input = listThreadEventsSchema.parse(payload); const companyError = ensureCompanyExists(input.companyId);
const companyError = ensureCompanyExists(input.companyId); if (companyError) return companyError;
if (companyError) return companyError; const events = db.listThreadEvents(input.threadId, input.companyId, {
const events = db.listThreadEvents(input.threadId, input.companyId, { limit: input.limit,
limit: input.limit, offset: input.offset,
offset: input.offset, });
}); return ok(events);
return ok(events);
} catch (error) {
return fail("THREAD_EVENTS_LIST_FAILED", error instanceof Error ? error.message : "Failed to list thread events");
}
}); });
registerHandle(IPC_CHANNELS.setThreadWaitState, async (_event, payload) => { registerHandle(IPC_CHANNELS.setThreadWaitState, async (_event, payload) => {
try { const input = setThreadWaitStateSchema.parse(payload);
const input = setThreadWaitStateSchema.parse(payload); const companyError = ensureCompanyExists(input.companyId);
const companyError = ensureCompanyExists(input.companyId); if (companyError) return companyError;
if (companyError) return companyError; const waitState = db.setThreadWaitState({
const waitState = db.setThreadWaitState({ threadId: input.threadId,
threadId: input.threadId, companyId: input.companyId,
companyId: input.companyId, waitingFor: input.waitingFor,
waitingFor: input.waitingFor, relatedApprovalId: input.relatedApprovalId ?? null,
relatedApprovalId: input.relatedApprovalId ?? null, reason: input.reason,
reason: input.reason, });
}); publishDomainChanged();
publishDomainChanged(); emitEvent({
emitEvent({ type: "thread-wait-state-updated",
type: "thread-wait-state-updated", threadId: input.threadId,
threadId: input.threadId, companyId: input.companyId,
companyId: input.companyId, waitStateId: waitState.id,
waitStateId: waitState.id, action: "created",
action: "created", });
}); return ok(waitState);
return ok(waitState);
} catch (error) {
return fail("THREAD_WAIT_STATE_SET_FAILED", error instanceof Error ? error.message : "Failed to set thread wait state");
}
}); });
registerHandle(IPC_CHANNELS.resolveThreadWaitState, async (_event, payload) => { registerHandle(IPC_CHANNELS.resolveThreadWaitState, async (_event, payload) => {
try { const input = resolveThreadWaitStateSchema.parse(payload);
const input = resolveThreadWaitStateSchema.parse(payload); const companyError = ensureCompanyExists(input.companyId);
const companyError = ensureCompanyExists(input.companyId); if (companyError) return companyError;
if (companyError) return companyError;
// Query the wait state before resolving to capture its threadId for the event // Query the wait state before resolving to capture its threadId for the event
const waitStates = db.listThreadWaitStates(input.companyId); const waitStates = db.listThreadWaitStates(input.companyId);
const target = waitStates.find((ws) => ws.id === input.waitStateId); const target = waitStates.find((ws) => ws.id === input.waitStateId);
if (!target) return fail("THREAD_WAIT_STATE_NOT_FOUND", "Wait state not found"); if (!target) return fail("THREAD_WAIT_STATE_NOT_FOUND", "Wait state not found");
db.resolveThreadWaitState(input.waitStateId, input.companyId); db.resolveThreadWaitState(input.waitStateId, input.companyId);
publishDomainChanged(); publishDomainChanged();
emitEvent({ emitEvent({
type: "thread-wait-state-updated", type: "thread-wait-state-updated",
threadId: target.threadId, threadId: target.threadId,
companyId: input.companyId, companyId: input.companyId,
waitStateId: input.waitStateId, waitStateId: input.waitStateId,
action: "resolved", action: "resolved",
}); });
return ok(true); return ok(true);
} catch (error) {
return fail("THREAD_WAIT_STATE_RESOLVE_FAILED", error instanceof Error ? error.message : "Failed to resolve thread wait state");
}
}); });
registerHandle(IPC_CHANNELS.listThreadWaitStates, async (_event, payload) => { registerHandle(IPC_CHANNELS.listThreadWaitStates, async (_event, payload) => {
try { const input = listThreadWaitStatesSchema.parse(payload);
const input = listThreadWaitStatesSchema.parse(payload); const companyError = ensureCompanyExists(input.companyId);
const companyError = ensureCompanyExists(input.companyId); if (companyError) return companyError;
if (companyError) return companyError; const waitStates = db.listThreadWaitStates(input.companyId, { resolvedOnly: input.resolvedOnly });
const waitStates = db.listThreadWaitStates(input.companyId, { resolvedOnly: input.resolvedOnly }); return ok(waitStates);
return ok(waitStates);
} catch (error) {
return fail("THREAD_WAIT_STATES_LIST_FAILED", error instanceof Error ? error.message : "Failed to list thread wait states");
}
}); });
registerHandle(IPC_CHANNELS.listParticipants, async (_event, payload) => { registerHandle(IPC_CHANNELS.listParticipants, async (_event, payload) => {
try { const input = listParticipantsSchema.parse(payload);
const input = listParticipantsSchema.parse(payload); const thread = db.getThread(input.threadId, input.companyId);
const thread = db.getThread(input.threadId, input.companyId); if (!thread) return fail("THREAD_NOT_FOUND", "Thread not found in this company");
if (!thread) return fail("THREAD_NOT_FOUND", "Thread not found in this company"); const participants = db.listParticipants(input.threadId);
const participants = db.listParticipants(input.threadId); return ok(participants);
return ok(participants);
} catch (error) {
return fail("PARTICIPANTS_LIST_FAILED", error instanceof Error ? error.message : "Failed to list participants");
}
}); });
registerHandle(IPC_CHANNELS.addParticipant, async (_event, payload) => { registerHandle(IPC_CHANNELS.addParticipant, async (_event, payload) => {
try { const input = addParticipantSchema.parse(payload);
const input = addParticipantSchema.parse(payload); const thread = db.getThread(input.threadId, input.companyId);
const thread = db.getThread(input.threadId, input.companyId); if (!thread) return fail("THREAD_NOT_FOUND", "Thread not found in this company");
if (!thread) return fail("THREAD_NOT_FOUND", "Thread not found in this company"); const participant = db.addParticipant({
const participant = db.addParticipant({ threadId: input.threadId,
threadId: input.threadId, participantId: input.participantId,
participantId: input.participantId, participantType: input.participantType,
participantType: input.participantType, role: input.role,
role: input.role, });
}); publishDomainChanged();
publishDomainChanged(); return ok(participant);
return ok(participant);
} catch (error) {
return fail("PARTICIPANT_ADD_FAILED", error instanceof Error ? error.message : "Failed to add participant");
}
}); });
registerHandle(IPC_CHANNELS.removeParticipant, async (_event, payload) => { registerHandle(IPC_CHANNELS.removeParticipant, async (_event, payload) => {
try { const input = removeParticipantSchema.parse(payload);
const input = removeParticipantSchema.parse(payload); const thread = db.getThread(input.threadId, input.companyId);
const thread = db.getThread(input.threadId, input.companyId); if (!thread) return fail("THREAD_NOT_FOUND", "Thread not found in this company");
if (!thread) return fail("THREAD_NOT_FOUND", "Thread not found in this company"); db.removeParticipant(input.threadId, input.participantId);
db.removeParticipant(input.threadId, input.participantId); publishDomainChanged();
publishDomainChanged(); return ok(true);
return ok(true);
} catch (error) {
return fail("PARTICIPANT_REMOVE_FAILED", error instanceof Error ? error.message : "Failed to remove participant");
}
}); });
registerHandle(IPC_CHANNELS.subscribeThread, async (_event, payload) => { registerHandle(IPC_CHANNELS.subscribeThread, async (_event, payload) => {
try { const input = subscribeThreadSchema.parse(payload);
const input = subscribeThreadSchema.parse(payload); const thread = db.getThread(input.threadId, input.companyId);
const thread = db.getThread(input.threadId, input.companyId); if (!thread) return fail("THREAD_NOT_FOUND", "Thread not found in this company");
if (!thread) return fail("THREAD_NOT_FOUND", "Thread not found in this company"); const subscription = db.subscribe({
const subscription = db.subscribe({ threadId: input.threadId,
threadId: input.threadId, subscriberId: input.subscriberId,
subscriberId: input.subscriberId, subscriberType: input.subscriberType,
subscriberType: input.subscriberType, notifyOn: input.notifyOn,
notifyOn: input.notifyOn, });
}); publishDomainChanged();
publishDomainChanged(); return ok(subscription);
return ok(subscription);
} catch (error) {
return fail("THREAD_SUBSCRIBE_FAILED", error instanceof Error ? error.message : "Failed to subscribe to thread");
}
}); });
registerHandle(IPC_CHANNELS.unsubscribeThread, async (_event, payload) => { registerHandle(IPC_CHANNELS.unsubscribeThread, async (_event, payload) => {
try { const input = unsubscribeThreadSchema.parse(payload);
const input = unsubscribeThreadSchema.parse(payload); const thread = db.getThread(input.threadId, input.companyId);
const thread = db.getThread(input.threadId, input.companyId); if (!thread) return fail("THREAD_NOT_FOUND", "Thread not found in this company");
if (!thread) return fail("THREAD_NOT_FOUND", "Thread not found in this company"); db.unsubscribe(input.threadId, input.subscriberId);
db.unsubscribe(input.threadId, input.subscriberId); publishDomainChanged();
publishDomainChanged(); return ok(true);
return ok(true);
} catch (error) {
return fail("THREAD_UNSUBSCRIBE_FAILED", error instanceof Error ? error.message : "Failed to unsubscribe from thread");
}
}); });
registerHandle(IPC_CHANNELS.listSubscriptions, async (_event, payload) => { registerHandle(IPC_CHANNELS.listSubscriptions, async (_event, payload) => {
try { const input = listSubscriptionsSchema.parse(payload);
const input = listSubscriptionsSchema.parse(payload); const thread = db.getThread(input.threadId, input.companyId);
const thread = db.getThread(input.threadId, input.companyId); if (!thread) return fail("THREAD_NOT_FOUND", "Thread not found in this company");
if (!thread) return fail("THREAD_NOT_FOUND", "Thread not found in this company"); const subscriptions = db.listSubscriptions(input.threadId);
const subscriptions = db.listSubscriptions(input.threadId); return ok(subscriptions);
return ok(subscriptions);
} catch (error) {
return fail("SUBSCRIPTIONS_LIST_FAILED", error instanceof Error ? error.message : "Failed to list subscriptions");
}
}); });
registerHandle(IPC_CHANNELS.listSubscribedThreads, async (_event, payload) => { registerHandle(IPC_CHANNELS.listSubscribedThreads, async (_event, payload) => {
try { const input = listSubscribedThreadsSchema.parse(payload);
const input = listSubscribedThreadsSchema.parse(payload); const threads = db.listSubscribedThreads(input.subscriberId, input.companyId);
const threads = db.listSubscribedThreads(input.subscriberId, input.companyId); return ok(threads);
return ok(threads);
} catch (error) {
return fail("SUBSCRIBED_THREADS_LIST_FAILED", error instanceof Error ? error.message : "Failed to list subscribed threads");
}
}); });
} }