# 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:

  1. Hits POST /v1/notifications/self
  2. Uses the signed-in user’s sub as recipient (safe — can’t target others)
  3. Adds a unique dedupeKey so 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();

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-Match304 when nothing changed
  • Pauses while the browser tab is hidden
  • Refreshes immediately when the tab becomes visible again
  • unreadCount changes 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() returns stop — call it on logout
  • useNotifications cleans 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.