Skip to content

About

Tiny live chat for Craft CMS 5 on Datastar + SSE. No cookies, no third-party service, office hours with an email fallback.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Quickchat for Craft CMS

A tiny live chat for the beta of a new project: visitors chat from a widget, you answer from one page in the control panel. Built on Datastar and server-sent events, it runs inside your Craft install, with no third-party service and no cookies.

  • Craft CMS 5.6+, PHP 8.2+
  • Datastar comes along as putyourlightson/craft-datastar-module; the Datastar plugin is optional

Installation

Until the first release is on Packagist, add the repository to your project's composer.json:

"repositories": [
    { "type": "vcs", "url": "https://github.com/larsmarkus-studio/craft-quickchat.git" }
]
composer require larsmarkus-studio/craft-quickchat
php craft plugin/install quickchat

Put the launcher in your layout, before </body>:

{{ craft.quickchat.widget() }}

Then set office hours, the support email and the texts under Settings → Plugins → Quickchat (also linked from the status badge on the dashboard), or in config/quickchat.php (every property of src/models/Settings.php, multi-environment aware).

Plugin settings are project config: with allowAdminChanges off in production, change the hours locally and deploy, or keep them in config/quickchat.php per environment.

What it does

  • Office hours: outside them, visitors see your support email instead of the chat. Open conversations stay readable and writable, with an "offline" note.
  • Optional email before the first message, so you can follow up after the visitor leaves.
  • Live both ways: the widget and the CP dashboard check for new messages every few seconds (see Polling or held streams).
  • Chime in the CP for new visitor messages, switched on per browser on the dashboard. Browsers only allow sound after you've clicked the page once; the dashboard says so if it was blocked.
  • Unread badge on the CP nav item, resolve / reopen per conversation. A visitor writing in a resolved chat reopens it.
  • Mail per new conversation to notifyEmail (optional).
  • Multi-site: each chat stores the site it started on, the CP shows it on one shared page, and visitor texts follow the site's language (translations/<lang>/quickchat.php, Dutch included).
  • Page URL the chat started on, shown in the CP.

Footprint

  • On page load: the launcher button, a few lines of inline CSS and datastar.js, only on pages with the widget. Load your own Datastar (e.g. Pro)? Set registerScript => false in config/datastar.php.
  • On the first click: the panel and its stylesheet arrive over SSE. The stylesheet is Bootstrap 5.3 cut down to modal, close button, buttons and form controls, scoped under .qc with --qc-* variables, so it never touches your own CSS (~4 KB gzipped).
  • No JavaScript besides Datastar. Signals live under qc, so they can't clash with yours.
  • The panel uses your site's body font. Accent colour and launcher font: set --qc-accent and --qc-launcher-font on .qc in your own CSS.

Privacy

Quickchat sets no cookies. The conversation key is a random token in the visitor's localStorage and is sent in POST bodies only, never in URLs. The visitor endpoints skip CSRF on purpose: CSRF protects cookie-based sessions, and these endpoints have none. Starting a chat is limited to five per IP per ten minutes and messages to one per second (through Craft's cache, where the IP sits hashed for those ten minutes; nothing about the visitor is stored besides what they type). Behind a load balancer or CDN, tell Craft which proxy headers to trust (trustedHosts, ipHeaders on the request component in config/app.php), or every visitor shares the proxy's IP and its limit.

Polling or held streams

By default the widget and the dashboard check for new messages every pollSeconds (3). Each check is a short Datastar request that answers at once (about 50 ms), so it never ties up a PHP process and works behind any proxy, buffering or compression. A message shows up within a few seconds.

On a server with PHP processes to spare, set holdSeconds (e.g. 30) for instant delivery: each request then stays open that long and pushes messages the moment they arrive. Every open chat and every open dashboard holds one PHP process for as long as it's open, and the server must pass streamed responses through unbuffered. If messages arrive in bursts, something buffers them. On Apache with PHP-FPM (DDEV's default), add .ddev/apache/sse-flush.conf:

<Proxy "unix:/var/run/php/php-fpm.sock|fcgi://localhost">
    ProxySet flushpackets=on
</Proxy>

Nginx needs nothing: the response sends X-Accel-Buffering: no.

Tests

php tests/hours.php                                                   # office-hours logic, no Craft needed
npm install && QUICKCHAT_URL=https://my-site.ddev.site/ npm run e2e   # Playwright, against a page with the widget

The e2e test checks that the stylesheet loads lazily, that a message round-trips and survives a reload, and that no quickchat response sets a cookie. Run it locally or on staging: it leaves an e2e … conversation behind.

Development

npm install && npm run build recompiles src/web/quickchat.css from build/quickchat.scss. python3 build/ding.py regenerates the chime, src/web/ding.wav. The compiled file is committed, so installing the plugin needs no Node.

Design decisions are in docs/decisions.

About

Tiny live chat for Craft CMS 5 on Datastar + SSE. No cookies, no third-party service, office hours with an email fallback.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages