> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sendrealm.com/llms.txt
> Use this file to discover all available pages before exploring further.

# React Web Push SDK

> Use @sendrealm/react to register browser Web Push subscriptions from React, Vite, and Next.js apps.

The Sendrealm React Web Push SDK registers browser Push API subscriptions with Sendrealm, keeps subscription state synced, and exposes React hooks for permission, identity, tags, diagnostics, and notification events.

This SDK runs in the browser and does not use a Sendrealm API key. It uses your Sendrealm Push App ID and the Web Push VAPID public key returned by the SDK API.

<Warning>
  Never put a Sendrealm API key in browser JavaScript. Use `@sendrealm/react`
  for browser device registration and `@sendrealm/sdk` from a trusted backend
  to send notifications.
</Warning>

## Requirements

* React 18 or newer.
* A Sendrealm Push App with the Web provider active.
* HTTPS in production. Browser vendors allow `localhost` for development.
* The Sendrealm service worker served from the same origin as your app.
* A user-initiated permission prompt, usually from an Enable notifications button.

iOS Web Push only works for installed Home Screen web apps. Your site must be served over HTTPS, include a valid web app manifest, be added to the Home Screen, and request notification permission from a user gesture inside the installed PWA.

## Install

```bash theme={null}
npm install @sendrealm/react
```

Copy the service worker into your public root:

```bash theme={null}
cp node_modules/@sendrealm/react/sendrealm-service-worker.js public/sendrealm-service-worker.js
```

Web Push service workers must be served from the same origin as the page. The default SDK path is `/sendrealm-service-worker.js` with scope `/`.

## Initialize In React Or Vite

The preferred React integration is a client-side `init()` call. No provider is required.

```tsx theme={null}
import { useEffect } from "react";
import { init, useSendrealmSubscription } from "@sendrealm/react";

const sendrealmAppId = "YOUR_SENDREALM_PUSH_APP_ID";

function NotificationButton() {
  const { subscribed, optIn, optOut } = useSendrealmSubscription();

  return (
    <button onClick={() => (subscribed ? optOut() : optIn())}>
      {subscribed ? "Disable notifications" : "Enable notifications"}
    </button>
  );
}

export function App() {
  useEffect(() => {
    void init({
      appId: sendrealmAppId,
      autoRequestPermission: false,
    });
  }, []);

  return <NotificationButton />;
}
```

`init()` is idempotent. React StrictMode can run effects twice during development; the SDK still sends only one initialization request for the default singleton client.

## Initialize In Next.js

Importing the package is SSR-safe, but initialization needs browser APIs. Put `init()` in a Client Component.

```tsx theme={null}
// app/sendrealm-init.tsx
"use client";

import { useEffect } from "react";
import { init } from "@sendrealm/react";

export function SendrealmInit() {
  useEffect(() => {
    void init({
      appId: process.env.NEXT_PUBLIC_SENDREALM_PUSH_APP_ID!,
      autoRequestPermission: false,
    });
  }, []);

  return null;
}
```

Render it once near your app shell:

```tsx theme={null}
// app/layout.tsx
import { SendrealmInit } from "./sendrealm-init";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <SendrealmInit />
        {children}
      </body>
    </html>
  );
}
```

Put `sendrealm-service-worker.js` in `public/` so Next serves it at `/sendrealm-service-worker.js`.

## Initialization Options

```ts theme={null}
init({
  appId: "YOUR_SENDREALM_PUSH_APP_ID",
  baseUrl: "https://sdk-api.sendrealm.com",
  environment: "production",
  autoRequestPermission: false,
  serviceWorkerPath: "/sendrealm-service-worker.js",
  serviceWorkerScope: "/",
  externalUserId: "user_123",
  userEmail: "user@example.com",
});
```

| Option                  | Default                         | Description                                                                                                                    |
| ----------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `appId`                 | Required                        | Sendrealm Push App ID from the dashboard.                                                                                      |
| `baseUrl`               | `https://sdk-api.sendrealm.com` | SDK API base URL. Use this for local development or self-hosted environments.                                                  |
| `environment`           | `production`                    | Device environment. Use `development` for test builds if your sends target development devices separately.                     |
| `autoRequestPermission` | `false`                         | If true, requests notification permission during initialization. Keep false unless initialization happens from a user gesture. |
| `serviceWorkerPath`     | `/sendrealm-service-worker.js`  | Same-origin URL for the copied service worker file.                                                                            |
| `serviceWorkerScope`    | `/`                             | Service worker scope. Use `/` unless your app intentionally isolates push to a subpath.                                        |
| `deviceId`              | Generated and stored            | Optional stable Sendrealm web device ID. Usually let the SDK manage this.                                                      |
| `externalUserId`        | `null`                          | Optional user ID to link during initialization. You can also call `login` after sign-in.                                       |
| `userEmail`             | `null`                          | Optional email to link during initialization.                                                                                  |

## Permission And Subscription UI

Browsers expect notification prompts to happen after a user action. A common pattern is to show your own explanation first, then call `optIn()` from a button click.

```tsx theme={null}
import { useSendrealmPermission, useSendrealmSubscription } from "@sendrealm/react";

export function PushSettings() {
  const { permissionStatus, requestPermission } = useSendrealmPermission();
  const { subscribed, optIn, optOut, refreshRegistrationToken } =
    useSendrealmSubscription();

  return (
    <section>
      <p>Permission: {permissionStatus}</p>
      <p>Subscribed: {subscribed ? "yes" : "no"}</p>
      <button onClick={() => void requestPermission()}>Ask permission</button>
      <button onClick={() => void optIn()}>Enable notifications</button>
      <button onClick={() => void optOut()}>Disable notifications</button>
      <button onClick={() => void refreshRegistrationToken(true)}>
        Refresh subscription
      </button>
    </section>
  );
}
```

`optIn()` requests permission if needed, subscribes through `PushManager.subscribe`, and registers the full `PushSubscription.toJSON()` payload with Sendrealm. `optOut()` unsubscribes the browser subscription and marks the Sendrealm device unsubscribed.

## Identity, Tags, And Events

Link the browser device after the user signs in:

```ts theme={null}
import { getSendrealmClient } from "@sendrealm/react";

const sendrealm = getSendrealmClient();

await sendrealm.login("user_123", "user@example.com");
```

Remove the identity on sign-out:

```ts theme={null}
await sendrealm.logout();
```

Add client-observed tags:

```ts theme={null}
await sendrealm.addTags({
  plan: "pro",
  onboarding_complete: true,
  locale: "en-US",
});
```

Track custom events:

```ts theme={null}
await sendrealm.trackEvent("checkout.started", {
  cart_id: "cart_123",
  total: 42,
});
```

Use tags for client-known state such as preferences, locale, feature flags, or onboarding progress. Use server-side contact properties for authoritative account, billing, compliance, and CRM data.

## Notification Events

Register listeners after `init()` or in a React effect that runs with the SDK client.

```tsx theme={null}
import { useEffect } from "react";
import { useSendrealm } from "@sendrealm/react";

export function NotificationEvents() {
  const { client } = useSendrealm();

  useEffect(() => {
    const opened = client.addNotificationClickListener(event => {
      console.log("opened", event.launchUrl, event.notificationId);
    });
    const action = client.addNotificationActionListener(event => {
      console.log("action", event.actionIdentifier);
    });
    const foreground = client.addForegroundNotificationListener(event => {
      console.log("foreground", event.payload);
    });
    const silent = client.addSilentNotificationListener(event => {
      console.log("silent", event.payload);
    });

    return () => {
      opened.remove();
      action.remove();
      foreground.remove();
      silent.remove();
    };
  }, [client]);

  return null;
}
```

The service worker also stores the last notification open so cold-start flows can read it:

```ts theme={null}
const initialOpen = await getSendrealmClient().getInitialNotification();
```

## Automatic Event Tracking

The browser SDK and service worker track notification lifecycle events when the SDK has initialized and the notification payload contains Sendrealm metadata.

| Event                                      | When it is tracked                                            |
| ------------------------------------------ | ------------------------------------------------------------- |
| `delivery`                                 | The service worker receives a Sendrealm notification payload. |
| `foreground_display`                       | A notification is received while a visible client exists.     |
| `background_notification_received`         | A notification is received while no visible client exists.    |
| `open`                                     | The user opens the notification.                              |
| `click`                                    | The user clicks the notification body or an action.           |
| `notification_action`                      | The user clicks a notification action button.                 |
| `dismiss`                                  | The browser fires `notificationclose`.                        |
| `push_subscription_changed`                | The browser fires `pushsubscriptionchange`.                   |
| `permission_granted` / `permission_denied` | The page observes a notification permission status change.    |

Browsers control which service worker events fire. For example, some browsers do not fire `notificationclose` for every dismissal.

## Launch URLs

When the user opens a notification, the service worker resolves the launch URL in this order:

1. The clicked action's `launch_url` or `launchUrl`.
2. Notification metadata `web_launch_url`.
3. Notification metadata `launch_url`.
4. Notification `data.launch_url`.
5. `/`.

Then it calls `clients.openWindow(launchUrl)` and notifies active page clients with `SENDREALM_NOTIFICATION_CLICKED`. In app code, listen with `addNotificationClickListener` or `addNotificationActionListener`.

Send a launch URL from the public API:

```ts theme={null}
await client.push.notifications.send({
  app_id: "push_app_short_id",
  external_ids: ["user_123"],
  platforms: ["web"],
  notification: {
    title: "Order update",
    body: "Your order is ready.",
    launch_url: "https://app.example.com/orders/123",
  },
});
```

## Rich Images And Actions

Sendrealm forwards notification images to the browser `showNotification({ image })` option when the browser supports rich web notification images. Browsers that do not support image rendering still show the title, body, icon, and badge.

Supported image fields include:

* `notification.image`
* `notification.imageUrl`
* `notification.image_url`
* `notification.web.image`
* `notification.web.imageUrl`
* `notification.web.image_url`
* `metadata.image_url`
* `metadata.imageUrl`

From the public API, use `notification.image_url`:

```ts theme={null}
await client.push.notifications.send({
  app_id: "push_app_short_id",
  audiences: ["audience_123"],
  platforms: ["web"],
  notification: {
    title: "New collection",
    body: "See what just launched.",
    image_url: "https://cdn.example.com/collection.png",
    launch_url: "https://app.example.com/collections/new",
  },
  buttons: [
    {
      id: "view",
      text: "View",
      launch_url: "https://app.example.com/collections/new",
    },
  ],
});
```

Web notifications render up to two action buttons because that is the browser notification API limit used by the service worker. Browser UI, image size, icons, action buttons, and grouping behavior vary by browser and operating system.

## Send To Web Devices

Once the SDK registers a browser, you can target web devices by:

* `device_ids`
* `contact_ids`
* `external_ids`
* `emails`
* `audiences`
* `platforms: ["web"]`

You can also send directly to raw browser subscriptions with `web_subscriptions` from a trusted backend. Do not collect raw browser subscriptions manually unless you have a specific direct-send use case; SDK-registered devices are easier to target and manage.

```ts theme={null}
await client.push.notifications.send({
  app_id: "push_app_short_id",
  external_ids: ["user_123"],
  platforms: ["web"],
  notification: {
    title: "Hello from Sendrealm",
    body: "This targets the user's registered web browsers.",
  },
});
```

## Diagnostics

Use diagnostics to confirm browser support, device ID, permission status, subscription state, service worker path, and the last SDK error.

```ts theme={null}
const diagnostics = await getSendrealmClient().getDiagnostics();
console.log(diagnostics);
```

For support flows, collect the redacted diagnostics payload:

```ts theme={null}
const diagnostics = await getSendrealmClient().getSupportDiagnostics();
```

## Hooks

| Hook                         | Returns                                                                                                                                   |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `useSendrealm()`             | `{ client, state, initializing, error }` for the default singleton client, or the nearest provider client if you use `SendrealmProvider`. |
| `useSendrealmPermission()`   | `{ permissionStatus, permissionGranted, requestPermission }`.                                                                             |
| `useSendrealmSubscription()` | `{ subscribed, token, optIn, optOut, refreshRegistrationToken }`.                                                                         |

The hooks subscribe to the default singleton client after `init()`. `SendrealmProvider` remains available for advanced apps that need to inject a custom `SendrealmWebClient`, but it is not required.

## Client API

| Method                                        | Description                                                                                    |
| --------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| `init(options)` / `initialize(options)`       | Initializes the default singleton client.                                                      |
| `getSendrealmClient()`                        | Returns the default singleton client.                                                          |
| `Sendrealm.initialize(options)`               | Initializes the exported singleton directly.                                                   |
| `login(userId, email?)`                       | Links the web device to an external user ID and optional email.                                |
| `logout()`                                    | Clears the linked identity from the device.                                                    |
| `requestPermission()`                         | Shows the browser notification permission prompt.                                              |
| `hasNotificationPermission()`                 | Returns whether permission is currently authorized.                                            |
| `getPermissionStatus()`                       | Returns `authorized`, `denied`, `not_determined`, `unsupported`, or a browser-specific status. |
| `getDeviceId()`                               | Returns the Sendrealm web device ID.                                                           |
| `isSubscribed()`                              | Returns whether the SDK currently has a registered browser subscription.                       |
| `getState()`                                  | Returns the SDK state snapshot.                                                                |
| `getDiagnostics()`                            | Returns SDK diagnostics.                                                                       |
| `getSupportDiagnostics()`                     | Returns diagnostics suitable for support collection.                                           |
| `refreshRegistrationToken(forceRefresh?)`     | Refreshes the browser PushSubscription and registers it.                                       |
| `optIn()`                                     | Enables web push for this browser.                                                             |
| `optOut()`                                    | Disables web push for this browser.                                                            |
| `addTag(key, value)` / `addTags(tags)`        | Writes SDK tags for this device/contact.                                                       |
| `removeTag(key)`                              | Removes an SDK tag.                                                                            |
| `trackEvent(event, properties?)`              | Tracks a custom event from the browser.                                                        |
| `getInitialNotification()`                    | Reads the notification open that launched the page, when available.                            |
| `addNotificationClickListener(listener)`      | Observes notification body opens.                                                              |
| `addNotificationActionListener(listener)`     | Observes action button clicks.                                                                 |
| `addForegroundNotificationListener(listener)` | Observes received push payloads while a page client is visible.                                |
| `addSilentNotificationListener(listener)`     | Observes silent/background-style payloads delivered to an active page client.                  |
| `addPermissionObserver(listener)`             | Observes notification permission changes.                                                      |
| `addSubscriptionObserver(listener)`           | Observes subscription token changes.                                                           |

Mobile-only APIs such as Android notification channels, APNs token injection, and Live Activities are unsupported or no-ops on web.

## Troubleshooting

### `Sendrealm Web Push requires window, Notification, serviceWorker, and PushManager`

`init()` ran somewhere without browser push APIs. In Next.js, move initialization to a Client Component and call it from `useEffect`.

### `Sendrealm Web Push public key is unavailable`

The Web provider is not active or the SDK API did not return `web_push.public_key`. Activate the Web provider for the Push App in the dashboard and try again.

### `Registration failed - push service error`

The browser exposed `PushManager` but could not create a subscription with its push service. This is common in embedded Chromium browsers, browsers with push messaging disabled, and local setups using a numeric loopback host.

Try:

* Open the app in a full Chrome, Edge, Brave, or Safari browser.
* Use `http://localhost:PORT` instead of `http://127.0.0.1:PORT`.
* Clear the origin's site data and service worker registrations.
* Confirm the browser can reach its push service.
* Confirm notification permission is not blocked at the browser or OS level.

### Notifications do not open the expected URL

Confirm the send payload includes `notification.launch_url` or an action-level `launch_url`. The browser must also allow the service worker to open the target URL.

### Images do not appear

Confirm the image URL is HTTPS, publicly reachable by the browser, and sent as `notification.image_url`. Some browsers ignore rich notification images even when the payload is valid.

## Local Demo

The package includes a demo app under `sdks/react/demo`.

```bash theme={null}
cd sdks/react
pnpm install
VITE_SENDREALM_APP_ID=YOUR_PUSH_APP_ID pnpm demo
```

Open the demo through `http://localhost:5174` rather than a numeric loopback address.
