@bayonai/push-notifications
v0.1.0
Published
Provider-neutral notification contracts with Capacitor Firebase and Firebase Admin adapters.
Maintainers
Readme
@bayonai/push-notifications
Provider-neutral notification contracts with optional Capacitor Firebase Messaging and Firebase Admin adapters.
Install
Install the package and only the peers used by the consuming runtime:
pnpm add @bayonai/push-notifications
pnpm add @capacitor/core @capacitor-firebase/messaging
pnpm add firebase-admin firebase-functionsDo not install @capacitor/push-notifications beside the Firebase Messaging
adapter. Both plugins compete for native push registration callbacks.
Entry points
@bayonai/push-notifications: messages, audiences, validation, providers, installation registries, job queues, and dispatcher.@bayonai/push-notifications/capacitor: framework-independent lifecycle controller.@bayonai/push-notifications/capacitor-firebase: dynamically loaded Capacitor FCM adapter.@bayonai/push-notifications/capacitor-local: dynamically loaded offline device scheduler using Capacitor Local Notifications.@bayonai/push-notifications/firebase-admin: Firestore registry, authenticated callable factories, and FCM provider.
The Firebase adapter also supports web browsers. Supply the Firebase Web Push VAPID public key and a host-owned service worker path; the adapter registers the worker only when a browser explicitly requests permission.
import { createFirebaseMessagingAdapter } from "@bayonai/push-notifications/capacitor-firebase";
const adapter = createFirebaseMessagingAdapter({
serviceWorkerPath: "/firebase-messaging-sw.js",
vapidKey: "your-public-vapid-key",
});Native code is dynamically imported only by the Capacitor Firebase entrypoint, so importing core or server contracts during SSR does not initialize Capacitor.
Offline device scheduling
Install @capacitor/local-notifications only in apps that schedule reminders on
the device. This adapter is independent from the selected remote push provider.
import { createLocalNotificationScheduler } from "@bayonai/push-notifications/capacitor-local";
const scheduler = createLocalNotificationScheduler();
await scheduler.schedule([
{
body: "You have a Bounded reminder.",
data: { openPath: "/notifications/open", schemaVersion: "1" },
id: 42,
scheduleAt: new Date(Date.now() + 60_000).toISOString(),
title: "Bounded",
},
]);OneSignal is intentionally not part of this package's active Bounded implementation. Do not install its SDK or adapters alongside Firebase Messaging; a future provider migration must preserve existing FCM registrations and be planned as a separate release.
Client lifecycle
import { createNotificationLifecycleController } from "@bayonai/push-notifications/capacitor";
import { createFirebaseMessagingAdapter } from "@bayonai/push-notifications/capacitor-firebase";
const controller = createNotificationLifecycleController({
adapter: createFirebaseMessagingAdapter(),
appId: "com.example.app",
provider: "fcm",
registerInstallation: async (installation) => api.register(installation),
revokeInstallation: async (installation) => api.revoke(installation),
});
await controller.start();
await controller.sync({
enabled: true,
identity: { ownerUserId: "authenticated-user-id" },
});Call requestPermissionAndSync only after an explicit user action. Call
disable before sign-out and stop when the host lifecycle is disposed.
Injected loggers must redact provider errors and must never serialize target
values; the package never intentionally emits a target as a standalone log
field.
Server delivery
import { createNotificationDispatcher } from "@bayonai/push-notifications";
import {
createFcmNotificationProvider,
createFirestoreInstallationRegistry,
} from "@bayonai/push-notifications/firebase-admin";
const registry = createFirestoreInstallationRegistry(firestore);
const dispatcher = createNotificationDispatcher({
appId: "com.example.app",
provider: createFcmNotificationProvider(messaging),
registry,
});
await dispatcher.send({
audience: { kind: "users", userIds: ["user-id"] },
message: {
title: "New notification",
body: "Open the app to review it.",
data: { schemaVersion: "1", notificationId: "notification-id" },
},
});Topics and provider conditions are broadcast mechanisms, not authorization.
Never place private content in topic messages. Scheduling requires an injected
NotificationJobQueue and deliberately owns no campaign database or UI.
Firestore contract
The Firebase registry stores pushInstallations/{sha256} with server-only raw
targets and ownership metadata. Host rules must deny all client access. The
callable factories validate authentication, app ID, provider, platform, and
target bounds before calling the registry.
Refresh registrations when an authenticated native app starts and when the
provider emits a new token. Remove invalid targets after delivery and schedule
pruneStale with an app-appropriate retention window.
Publishing
pnpm test
pnpm lint
pnpm build
pnpm consumer:smoke
pnpm pack:smokePublishing is intentionally separate from implementation. Bump the version and changelog, verify npm scope authorization, publish with public access, and test the registry artifact in a fresh Capacitor consumer before changing apps from workspace consumption.
