126 lines
3.7 KiB
JavaScript
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(/\/+$/, "") : "";
|
|
}
|