105 lines
4.7 KiB
TypeScript
105 lines
4.7 KiB
TypeScript
import type { Backend } from "./Backend";
|
|
import { mockBackend } from "./mock";
|
|
import { iios } from "@/lib/iios/client";
|
|
import { toInboxItem, toMessage, toTicket } from "@/lib/iios/map";
|
|
import manifest from "@/lib/iios/seed-manifest.json";
|
|
import type { Bootstrap, InboxState, Message, TicketState } from "@lynkd/messaging-inbox-sdk";
|
|
|
|
// If the IIOS DB has been seeded (scripts/seed-iios.mjs), a manifest maps the real
|
|
// IIOS thread ids to a channel directory so the sidebar shows live data. Otherwise
|
|
// bootstrap/channels fall back to the mock store (IIOS has no channel-list endpoint).
|
|
const seeded = (manifest as any)?.seeded === true;
|
|
const seededBootstrap = () => manifest as unknown as Bootstrap;
|
|
|
|
/**
|
|
* IIOS-backed implementation. Endpoints that map cleanly to the IIOS API are wired
|
|
* for real (messages, threads, inbox, tickets). Concepts IIOS doesn't expose 1:1 yet
|
|
* (bootstrap/users/channels, reactions, search, notifications, availability) delegate
|
|
* to the mock backend so the UI keeps working — replace these as IIOS grows portal/
|
|
* directory endpoints. See docs/IIOS_INTEGRATION.md.
|
|
*/
|
|
export const iiosBackend: Backend = {
|
|
// --- bootstrap/channels: from the seed manifest if present, else mock ---
|
|
bootstrap: () => (seeded ? Promise.resolve(seededBootstrap()) : mockBackend.bootstrap()),
|
|
listChannels: () => (seeded ? Promise.resolve(seededBootstrap().channels) : mockBackend.listChannels()),
|
|
createChannel: (input) => mockBackend.createChannel(input),
|
|
addMember: (channelId, userId) => mockBackend.addMember(channelId, userId),
|
|
removeMember: (channelId, userId) => mockBackend.removeMember(channelId, userId),
|
|
markRead: (channelId) => mockBackend.markRead(channelId),
|
|
setStatus: (text, emoji, clearAt) => mockBackend.setStatus(text, emoji, clearAt),
|
|
updateMe: (patch) => mockBackend.updateMe(patch),
|
|
availability: () => mockBackend.availability(),
|
|
notifications: () => mockBackend.notifications(),
|
|
|
|
// IIOS has no reaction/pin/save/edit/delete endpoints — keep local behavior
|
|
react: (channelId, messageId, emoji) => mockBackend.react(channelId, messageId, emoji),
|
|
pin: (channelId, messageId) => mockBackend.pin(channelId, messageId),
|
|
save: (channelId, messageId) => mockBackend.save(channelId, messageId),
|
|
editMessage: (channelId, messageId, body) => mockBackend.editMessage(channelId, messageId, body),
|
|
deleteMessage: (channelId, messageId) => mockBackend.deleteMessage(channelId, messageId),
|
|
savedMessages: () => mockBackend.savedMessages(),
|
|
threadParents: () => mockBackend.threadParents(),
|
|
|
|
// IIOS has no cross-entity search endpoint documented — use local search
|
|
search: (q) => mockBackend.search(q),
|
|
|
|
// --- wired to the real IIOS service ---
|
|
async channelMessages(channelId) {
|
|
const r = await iios.getThreadMessages(channelId);
|
|
const list = Array.isArray(r) ? r : (r.messages ?? []);
|
|
return list.filter((m: any) => !(m.parentInteractionId ?? m.parentId)).map((m: any) => toMessage(m, channelId));
|
|
},
|
|
|
|
async thread(channelId, parentId) {
|
|
const r = await iios.getThreadMessages(channelId);
|
|
const list: Message[] = (Array.isArray(r) ? r : r.messages ?? []).map((m: any) => toMessage(m, channelId));
|
|
return {
|
|
parent: list.find((m) => m.id === parentId) ?? null,
|
|
replies: list.filter((m) => m.parentId === parentId),
|
|
};
|
|
},
|
|
|
|
async send(channelId, body, opts) {
|
|
const idem = `web-${channelId}-${body.length}-${Math.round((opts?.scheduledFor ?? 0) / 1000)}`;
|
|
const r = await iios.sendMessage(channelId, body, idem);
|
|
return toMessage(r.message ?? r, channelId);
|
|
},
|
|
|
|
async listInbox(state) {
|
|
const r = await iios.listInbox(state);
|
|
const list = Array.isArray(r) ? r : r.items ?? [];
|
|
return list.map(toInboxItem);
|
|
},
|
|
|
|
async patchInbox(id, state: InboxState) {
|
|
const r = await iios.patchInbox(id, state);
|
|
return toInboxItem(r.item ?? r);
|
|
},
|
|
|
|
async listTickets(scope) {
|
|
const r = await iios.listTickets(scope);
|
|
const list = Array.isArray(r) ? r : r.tickets ?? [];
|
|
return list.map(toTicket);
|
|
},
|
|
|
|
async getTicket(id) {
|
|
// IIOS lists tickets; fetch and find (swap for a GET /tickets/:id when available)
|
|
const r = await iios.listTickets("all");
|
|
const list = Array.isArray(r) ? r : r.tickets ?? [];
|
|
const found = list.find((t: any) => String(t.id) === id || String(t.ref) === id);
|
|
return found ? toTicket(found) : null;
|
|
},
|
|
|
|
async patchTicket(id, state: TicketState) {
|
|
const r = await iios.patchTicket(id, state);
|
|
return toTicket(r.ticket ?? r);
|
|
},
|
|
|
|
async replyTicket(id, body) {
|
|
// A ticket reply is a message on the ticket's linked thread.
|
|
const t = await this.getTicket(id);
|
|
if (t?.channelId) await iios.sendMessage(t.channelId, body);
|
|
return this.getTicket(id);
|
|
},
|
|
};
|