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

@nethesis/chat-island

v0.1.32

Published

NethVoice chat island: a stand-alone chat widget (XMPP) embeddable in nethvoice-cti, NethLink and any web app

Readme

NethVoice Chat Island

A fully standalone chat component for NethVoice: one-to-one and group chats, attachments, voice notes, reactions, replies and notifications.

It runs in NethVoice CTI, in NethLink and in any web page, and talks to the per-tenant XMPP server of ns8-nethvoice with the user's CTI token.

Try the live demo

Builds

Available as component on npm

npm

Available as widget on jsDelivr

jsdelivr-js jsdelivr-css

Use it as a React component

npm install @nethesis/chat-island
import { ChatIsland } from '@nethesis/chat-island'
import '@nethesis/chat-island/dist/index.css'

<ChatIsland dataConfig={btoa(`${ctiHost}:${username}:${ctiToken}`)} serviceWorker="/chat-island-sw.js" />

| Prop | Default | | | --- | --- | --- | | dataConfig | | Base64 of <cti_host>:<cti_username>:<cti_token> | | theme | host's dark class | light, dark or system | | position | bottom-right | or bottom-left | | serviceWorker | | URL of chat-island-sw.js on the host: notifications with the tab closed | | notifications | click | auto asks for permission once online | | newChatButton | true | the new chat button while there are no chats; false when the host has its own list (a + under the heads stays) | | maxHeads | 5 | chat heads in the dock; the others wait behind a +N bubble | | sound | true | chime for a message not in view | | headless | false | connected but invisible: events only, no sound, notifications, push or read receipts | | drag / onDragStart | true | off, or the host moves its own window (NethLink) | | nativeTooltips | false | the browser's tooltips for the heads' names, for a host window sized to the dock (NethLink) | | pinnable | false | the host has a side rail and panel the chat can be pinned to (see below) |

The expand button in a chat's header doubles its width (one and a half times its height), within the viewport: a host whose window follows the island, like NethLink, lifts that cap with --ci-max-w / --ci-max-h on .chat-island-root.

With pinnable the chat menu offers Pin to side panel: the host mounts two slots and announces them with chat-island-rail-target and chat-island-pin-target { el } (again on every chat-island-status, since the island may come up later). While the rail slot is there nothing floats: the chat heads render in it (no X: the panel's X takes a chat out and shows the next one) and the open conversation, or the new chat panel, fills the panel slot. chat-island-window { open, picker } tells the host when to show or hide its panel, chat-island-pin { pinned } when the person pins or unpins; the host keeps that choice.

A group's owner changes its picture, name and members from the chat menu (Group settings); each change is a quiet line in the group (no body: no push, no unread) that every member's client refreshes on.

contactsFromOperators(operators, avatars, me) turns the CTI operators into the contacts the island takes with chat-island-contacts.

Integrate in any template

The widget works in any HTML page, CMS or server-rendered application, without React. You need:

  1. the CDN CSS file;
  2. the CDN JavaScript bundle;
  3. an element with class chat-island and a Base64 config token in data-config;
  4. optionally, a script of yours that sends commands and listens to the island's browser events.
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@nethesis/chat-island@latest/dist-widget/index.widget.css" />
  </head>
  <body>
    <div class="chat-island" data-config="YOUR_BASE64_CONFIG_TOKEN" data-theme="system"></div>

    <script src="https://cdn.jsdelivr.net/npm/@nethesis/chat-island@latest/dist-widget/index.widget.js"></script>
    <script>
      window.addEventListener('chat-island-unread', (e) => (document.title = `(${e.detail.total}) My page`))
      // open the chat with an operator
      window.dispatchEvent(new CustomEvent('chat-island-open', { detail: { username: 'mario' } }))
    </script>
  </body>
</html>

The widget mounts on the .chat-island elements present when its script runs. Attributes:

| Attribute | | | --- | --- | | data-config | Base64 config token (required) | | data-theme | light, dark or system | | data-position | bottom-right or bottom-left | | data-service-worker | URL of chat-island-sw.js, served by your site (same origin) | | data-notifications | auto to ask for permission once online | | data-new-chat-button | false to hide the new chat button |

Base64 config token

<cti_host>:<cti_username>:<cti_token>

The CTI token comes from the NethVoice middleware login:

TOKEN=$(curl -s -X POST https://cti.example.com/api/login \
  -H 'Content-Type: application/json' -d '{"username":"mario","password":"..."}' | jq -r .token)
echo -n "cti.example.com:mario:$TOKEN" | base64 -w0

The chat must be on in NethVoice and the user's CTI profile must have the Chat permission. With two-factor authentication, use a token from a QR code login.

A complete example page is in widget-example/; the live demo is index.html.

Events

| Event | Direction | Detail | | --- | --- | --- | | chat-island-open | in | { username } operator or group address | | chat-island-close | in | | | chat-island-new | in | open the new chat panel | | chat-island-delete | in | { username } purge a conversation, leave or destroy a group | | chat-island-send | in | { username, text } send a text to an operator or group | | chat-island-group-create | in | { name, members } create a group with these usernames and open it | | chat-island-contacts | in | { contacts } names, avatars, presence, number; an entry without avatar keeps the known one | | chat-island-conversations-request | in | ask for the list | | chat-island-theme-change | in | { theme } light, dark or system, remembered | | chat-island-rail-target | in | { el } the host's rail slot for the heads while pinned; null to unpin | | chat-island-pin-target | in | { el } the host's panel for the open conversation while pinned | | chat-island-conversations | out | { conversations } newest first | | chat-island-unread | out | { total } | | chat-island-message | out | every new message | | chat-island-status | out | { status } connecting, online, offline, error, unauthorized | | chat-island-error | out | { scope, message } | | chat-island-push | out | { enabled } | | chat-island-call | out | { username, number } | | chat-island-pin | out | { pinned } the person asked to pin to, or unpin from, the host's panel | | chat-island-window | out | { open, picker } what a pinned host should show: a conversation, the picker or nothing | | chat-island-notify | out | { peer, name, body, kind, author, text, attachment, avatar, ts } message not in view |

Integrate in an existing app

An app with its own users (WebTop, an intranet) embeds the widget and builds its view on the events:

  1. log the user in on the CTI (/api/login, or a QR code token with two-factor authentication) and pass <cti_host>:<username>:<token> as data-config;
  2. on chat-island-status unauthorized, log in again and set the new token in data-config: the island reconnects in place;
  3. keep its conversation list with chat-island-conversations-request / chat-island-conversations, badges with chat-island-unread;
  4. drive the island with chat-island-open, chat-island-close, chat-island-send, chat-island-group-create, chat-island-delete; newChatButton: false hides the island's entry point while there are no chats (the + under the heads stays);
  5. serve chat-island-sw.js from its own origin for notifications with the tab closed.
const send = (name, detail) => window.dispatchEvent(new CustomEvent(name, { detail }))
window.addEventListener('chat-island-conversations', (e) => render(e.detail.conversations))
send('chat-island-conversations-request')
send('chat-island-group-create', { name: 'Support', members: ['mario', 'anna'] })
send('chat-island-send', { username: 'mario', text: 'Ciao!' })

Live demo

index.html is published on GitHub Pages by .github/workflows/pages.yml at every push to main, with the widget built from the same commit. It logs in with NethVoice credentials (or a token), mounts the widget and shows its events.

npm install
npm run build:widget
python3 -m http.server   # then open http://localhost:8000/

Develop

npm install
npm run dev
npm run build && npm run build:widget

Release with npm run publish:patch (or :minor, :major): it checks the version is free on npm, type checks, commits and tags vX.Y.Z, and pushes. The tag publishes the package to npm; jsDelivr serves the widget from it.