npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@offerberries/communication-ui

v0.5.0

Published

OfferBerries Channels UI — embeddable chat surface and standalone messenger. Shares the --ob-* token layer with the ERP and Marketplace.

Readme

@offerberries/communication-ui

The Channels UI — an embeddable chat surface, and the standalone messenger at channels.offerberriesvo.com. It talks to the Channels service (offerberries-communication), which owns accounts, channels, messages, documents and realtime delivery.

What this package is, and what it deliberately is not

Channels owns the conversation. The host owns the thing being discussed.

That rule is the product's, not a convention of this package — see docs/OWNERSHIP_BOUNDARY_AND_SEQUENCE.md in the service repo. A buyer and a seller talk in Channels, but the order never leaves the host. Channels holds the thread and an opaque reference to the subject, and it never learns what an order is.

Practically, that means this package will not:

  • resolve your identifiers against your system,
  • know what your domain objects are, or
  • decide which kind of conversation is appropriate for your users.

The last one is why presets is part of the configuration rather than a built-in table. See below.

Install

npm install @offerberries/communication-ui

react, react-dom and react-router-dom are peer dependencies — you supply them, so there is exactly one copy of each on the page. A second router in particular is not a size problem but a correctness one: our components would resolve against a router nothing navigates, and every link would render and do nothing.

Configure once, before anything renders

import { configureChannels } from '@offerberries/communication-ui';
import '@offerberries/communication-ui/style.css';

configureChannels({
  baseUrl: '/api',
  presets: {
    business: {
      direct: 'acme.buyerSeller',
      group: 'acme.job',
      broadcast: null, // not offered here
    },
  },
});

baseUrl

Where the Channels service is. Points both the REST client and the session store at one service.

presets — required in practice

A preset key names the template a channel is created from: who may post, who may read, which party types are allowed. Preset keys are your domain's words (acme.buyerSeller, acme.job), registered against your origin in the service's Preset collection, and this package cannot invent them — choosing between "a buyer–seller chat" and "a job chat" means knowing what a job is.

A tier with no configured presets offers no conversation types, and the UI says so explicitly rather than rendering a form that refuses every submit. If you see "No conversation types are available here", this is the field to set.

Register the preset in the service first; nothing on the client validates these keys, and POST /channels refuses an unknown one.

Theme

Set the tier on your root element so the accent matches the host:

<html data-tier="corporate">   <!-- ERP -->
<html data-tier="business">    <!-- Marketplace -->

Tier overlays are separate imports on purpose — a host picks one, and bundling both would ship a palette you do not use:

import '@offerberries/communication-ui/theme/marketplace.css';

If your app authenticates its own users

This is the C-1 path, and the usual one for an embedding host: Channels never owns users. Your backend calls the service's token endpoint, gets a Channels access token for an already-authenticated person, and hands it here.

import { adoptSession } from '@offerberries/communication-ui';

const { token } = await yourBackend.mintChannelsToken();

adoptSession({
  accessToken: token,
  tier: 'business',
  // Optional. How YOU mint a replacement, called shortly before expiry.
  renew: async () => (await yourBackend.mintChannelsToken()).token,
});

Everything else — account id, display name, email, tenant — is read off the token's claims. There is nothing to reassemble.

An adopted session behaves differently from one signed in through this package, in three ways that all exist to stop your users being signed out by machinery that assumes it owns them:

  • It renews through your renew, never through /auth/refresh. The service never issued this session id, so refreshing it there returns 401 — and a 401 means "revoked", which would drop the session on a timer, mid-conversation.
  • It survives restore(). That call asks which sessions this device's refresh cookie covers; yours is deliberately not among them, so its absence from the answer means nothing.
  • signOut() drops it locally and does not call /auth/revoke, because there is nothing there to revoke. It reports revoked: true, which is the honest answer.

It is also kept out of the account-switcher cache, since that cache means "this device can resume this account by itself" and only you can re-mint it.

Without renew, the session simply stops working when the token expires. Nothing drops it, so you can re-adopt at any time.

The stylesheet does not reset your document

@offerberries/communication-ui/style.css carries our design tokens (--ob-*) and our component classes (.ob-*) and nothing that reaches outside them — no * reset, no html/body rules, no bare button, input or a styling.

That was not always true. Until 0.5.0 the published stylesheet was the standalone app's, reset and all, so importing it restyled a host's entire document. It was found when a host's own tab bar collapsed to unstyled text the moment an embedded surface mounted — and nothing about that looked like a stylesheet problem, because the tabs still rendered, still worked and still passed their tests.

The trade is deliberate: an embedded surface inherits YOUR focus rings, scrollbars and reduced-motion policy rather than ours. A component that overrode its host's focus treatment would be a worse citizen than one that adopts it.

Embedding a surface

import { ConversationListPane, Thread, ErrorBoundary } from '@offerberries/communication-ui';

<ErrorBoundary fallback={<YourEmptyState />} onError={report}>
  <Thread channelId={id} />
</ErrorBoundary>

Wrap what you embed. With no boundary between us, a throw inside this package takes down your chrome too, and you cannot see it coming. The default fallback renders our own ErrorState, so it matches the rest of the product if you supply nothing.

Thread takes an optional channelId; without it, it reads :channelId from the route. The prop lets your url stay shaped around your app rather than our routing. It does not make Thread router-free — it still uses useNavigate, useLocation and Link internally, so render it inside a router context either way.

Building

npm run build:lib        # ESM + UMD bundles, CSS, and .d.ts into dist-lib/
npm run build:standalone # the hosted messenger
npm run typecheck
npm run lint

prepublishOnly runs the typecheck and the library build, so a publish cannot ship a stale bundle.

Licence

UNLICENSED — all rights reserved. This package is published for use by OfferBerries and its integration partners; it is not open source.