NODEDC_LAUNCHER/server/notification-core-client.mjs

126 lines
3.7 KiB
JavaScript

export function createNotificationCoreClient({ baseUrl, token }) {
const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
const internalToken = typeof token === "string" ? token.trim() : "";
return {
isConfigured() {
return Boolean(normalizedBaseUrl && internalToken);
},
async createEvent(command) {
return requestJson({
baseUrl: normalizedBaseUrl,
token: internalToken,
method: "POST",
pathname: "/internal/notifications/events",
body: command,
});
},
async listNotifications({ subject, surface, limit, unreadOnly }) {
const searchParams = new URLSearchParams();
searchParams.set("surface", surface || "hub");
if (limit) searchParams.set("limit", String(limit));
if (unreadOnly) searchParams.set("unreadOnly", "1");
return requestJson({
baseUrl: normalizedBaseUrl,
token: internalToken,
method: "GET",
pathname: `/api/notifications?${searchParams.toString()}`,
subject,
});
},
async markNotificationRead({ subject, deliveryId }) {
return requestJson({
baseUrl: normalizedBaseUrl,
token: internalToken,
method: "POST",
pathname: `/api/notifications/${encodeURIComponent(deliveryId)}/read`,
subject,
});
},
async markAllNotificationsRead({ subject, surface }) {
const searchParams = new URLSearchParams();
searchParams.set("surface", surface || "hub");
return requestJson({
baseUrl: normalizedBaseUrl,
token: internalToken,
method: "POST",
pathname: `/api/notifications/read-all?${searchParams.toString()}`,
subject,
});
},
async openNotificationStream({ subject, surface, signal }) {
if (!normalizedBaseUrl || !internalToken) {
throw new Error("notification_core_not_configured");
}
const searchParams = new URLSearchParams();
searchParams.set("surface", surface || "hub");
const response = await fetch(`${normalizedBaseUrl}/api/notifications/stream?${searchParams.toString()}`, {
method: "GET",
headers: buildHeaders(internalToken, subject),
signal,
});
if (!response.ok || !response.body) {
throw new Error(await readError(response));
}
return response;
},
};
}
async function requestJson({ baseUrl, token, method, pathname, body, subject }) {
if (!baseUrl || !token) {
throw new Error("notification_core_not_configured");
}
const response = await fetch(`${baseUrl}${pathname}`, {
method,
headers: buildHeaders(token, subject, body !== undefined),
body: body === undefined ? undefined : JSON.stringify(body),
});
let parsed = null;
try {
parsed = await response.json();
} catch {}
if (!response.ok) {
const error = new Error(parsed?.error ? String(parsed.error) : `notification_core_http_${response.status}`);
error.status = response.status;
error.payload = parsed;
throw error;
}
return parsed;
}
function buildHeaders(token, subject, hasJsonBody = false) {
const headers = {
Accept: "application/json",
Authorization: `Bearer ${token}`,
};
if (hasJsonBody) headers["Content-Type"] = "application/json";
if (subject?.userId) headers["X-NODEDC-User-Id"] = subject.userId;
if (subject?.email) headers["X-NODEDC-User-Email"] = subject.email;
return headers;
}
async function readError(response) {
try {
const parsed = await response.json();
if (parsed?.error) return String(parsed.error);
} catch {}
return `notification_core_http_${response.status}`;
}
function normalizeBaseUrl(value) {
return typeof value === "string" && value.trim() ? value.trim().replace(/\/+$/, "") : "";
}