# Everyday usage
Once the client exists, day-to-day work is mostly: show the inbox, react to clicks, keep the badge honest.
# Toasts vs notifications (please read this)
| Toast | Notification (this service) | |
|---|---|---|
| Lifespan | Seconds | Days (until deleted / retention) |
| Where | Floating banner | Bell / inbox |
| After refresh | Gone | Still there |
| Who owns UI | Your app | Your app (data from us) |
It’s normal to use both in one flow:
try {
await riskyOperation();
toast.success('Success!'); // ephemeral — your toast lib
} catch (error) {
toast.error('Operation failed');
await notification.error('Failed', error.message, '/help'); // persistent inbox
}
This package does not ship toast.*. Keep your existing toast helper.
# Configure once, use everywhere
Recommended app pattern:
// lib/notifications.ts
import {
NotificationsClient,
createNotificationService,
} from '@inovus-medical/notifications';
export const notificationsClient = new NotificationsClient({
baseUrl: import.meta.env.VITE_NOTIFICATIONS_URL,
getAccessToken: () => auth.getValidAccessToken(),
});
// V1-shaped helpers for self-notifications
export const notification = createNotificationService(notificationsClient);
Call sites stay boring and familiar:
import { notification } from './lib/notifications';
await notification.success('Title', 'Message', '/optional-url');
await notification.error('Error Title', 'Error details');
await notification.warning('Warning', 'Something needs attention');
await notification.info('Info', 'Just so you know');
Under the hood each call:
- Hits
POST /v1/notifications/self - Uses the signed-in user’s
subas recipient (safe — can’t target others) - Adds a unique
dedupeKeyso that attempt’s retries won’t double-create
# Inbox operations
import { notificationsClient } from './lib/notifications';
// List (newest first). unreadCount is the *total*, not just this page.
const page = await notificationsClient.list({ limit: 50, unreadOnly: true });
await notificationsClient.markRead(id);
await notificationsClient.markUnread(id);
await notificationsClient.markAllRead();
await notificationsClient.delete(id);
const count = await notificationsClient.unreadCount();
# Handling deep links
When the user clicks a row:
function onNotificationClick(n: Notification) {
void notificationsClient.markRead(n.id);
if (n.actionUrl) {
router.push(n.actionUrl); // your router
}
}
We store actionUrl; you navigate.
# Keeping the bell up to date
# Option A — subscribe() (framework-agnostic)
const stop = notificationsClient.subscribe(
({ items, unreadCount }) => {
setItems(items);
setBadge(unreadCount);
},
{ intervalMs: 30_000 },
);
// logout / teardown
stop();
Behaviour you get for free:
- ETag /
If-None-Match→304when nothing changed - Pauses while the browser tab is hidden
- Refreshes immediately when the tab becomes visible again
unreadCountchanges outside the first page still wake the listener
# Option B — React hook
import { useNotifications } from '@inovus-medical/notifications/react';
const {
notifications,
unreadCount,
loading,
error,
markRead,
markUnread,
markAllRead,
remove,
} = useNotifications(notificationsClient);
Optimistic updates apply locally; the next poll reconciles with the server.
# Severity guide
| Severity | Typical use |
|---|---|
info |
Neutral FYI |
success |
Something finished well |
warning |
Needs attention, not broken |
error |
Failure the user should know about |
Your UI maps these to icons/colours — same four values as V1, so existing mappings often carry over.
# Errors
Failed calls throw NotificationsApiError after retries:
import { NotificationsApiError } from '@inovus-medical/notifications';
try {
await notificationsClient.markRead(id);
} catch (err) {
if (err instanceof NotificationsApiError) {
console.error(err.status, err.problem?.detail ?? err.message);
}
}
V2 API errors use RFC 9457 application/problem+json. The client parses that for you.
# Logout hygiene
V1 leaked subscriptions because cleanup was easy to forget. In V2:
subscribe()returnsstop— call it on logoutuseNotificationscleans up on unmount automatically- Create a fresh client (or at least stop polls) when the user session ends
Next: Publishing for backend / M2M flows, or V1 compatibility if you have legacy callers.