@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
Keywords
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.
Builds
Available as component on npm
Available as widget on jsDelivr
Use it as a React component
npm install @nethesis/chat-islandimport { 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:
- the CDN CSS file;
- the CDN JavaScript bundle;
- an element with class
chat-islandand a Base64 config token indata-config; - 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 -w0The 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:
- log the user in on the CTI (
/api/login, or a QR code token with two-factor authentication) and pass<cti_host>:<username>:<token>asdata-config; - on
chat-island-statusunauthorized, log in again and set the new token indata-config: the island reconnects in place; - keep its conversation list with
chat-island-conversations-request/chat-island-conversations, badges withchat-island-unread; - drive the island with
chat-island-open,chat-island-close,chat-island-send,chat-island-group-create,chat-island-delete;newChatButton: falsehides the island's entry point while there are no chats (the+under the heads stays); - serve
chat-island-sw.jsfrom 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:widgetRelease 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.
