Silent receipt and label printing for web applications, for both Chrome and Firefox, built from a single shared source tree. Olorin was developed for the Koha ILS and works with Koha out of the box, but it is not tied to Koha — any web page that embeds its print markup can use it.
The extension works together with the
Olorin Companion App,
a small desktop app that does the actual printing. The extension finds print
buttons embedded in the page, grabs the designated element’s HTML, and
sends it to the companion app over a local WebSocket (ws://localhost:9696).
Nothing ever leaves the computer.
The web application embeds a print button — in Koha, notice/slip templates carry it:
<button id="webPrint" data-printer="receipt_printer" data-print="#receipt">Print</button>
data-printer names one of five logical printers — receipt_printer,
sticker_printer, paper_printer, full_sheet_printer,
label_printer — and data-print is a CSS selector for the element whose
contents should print. Any element with the olorinPlugin class works the
same way.
Wrap the printable part of the slip:
<span id="receipt"> ... slip content ... </span>
To print automatically when the page loads, trigger a click on the
button from the page’s own JavaScript. In Koha that means setting the
IntranetSlipPrinterJS system preference to:
setTimeout(function () {
$("#webPrint").trigger("click");
}, 1000);
(Any page script that dispatches a real click works — jQuery is not required.)
The extension’s options page maps each logical printer to a real OS printer, with per-printer page size, margins, orientation, number of copies, and duplex (long edge / short edge) mode. Settings are stored by the companion app, so they are shared by every browser on the machine.
Pages can react to print outcomes by listening for a DOM event:
document.addEventListener("olorin:print-result", (e) => console.log(e.detail));
// e.detail = { success: true } or { success: false, error: "..." }
A button with data-action="cash-drawer" kicks open the till drawer
attached to the printer named by data-printer instead of printing (no
data-print is needed):
<button class="olorinPlugin" data-action="cash-drawer" data-printer="receipt_printer">
Open Drawer
</button>
The outcome is reported as an olorin:drawer-result DOM event with the
same { success, error } detail shape as olorin:print-result.
Nothing in Olorin depends on Koha. Two integration styles are available; print buttons need zero JavaScript, while the event API is the recommended path for applications that drive printing from their own code. Either way, printer mappings, page sizes, margins, copies, and duplex are configured once per workstation in the extension’s Settings page or the companion app’s window — the page never needs to know about physical printers.
webPrint or class olorinPlugin carrying
data-printer (one of the five logical printer keys) and data-print
(a CSS selector for the element to print) — or
data-action="cash-drawer" plus data-printer to open a till drawer.innerHTML is printed silently.olorin:print-result / olorin:drawer-result DOM events
to react to outcomes.The recommended programmatic path — used by the Koha plugin’s client
library, for example — is to dispatch CustomEvents on document instead
of synthesizing clicks on a hidden button. Available from extension 2.1:
| Request event | Detail fields | Result event |
|---|---|---|
olorin:ping |
id? |
olorin:pong |
olorin:status |
id? |
olorin:status-result |
olorin:print |
id?, printer, selector or content, overrides |
olorin:print-result |
olorin:kick-drawer |
id?, printer |
olorin:drawer-result |
// Detect the extension: ping is answered immediately with the version
document.addEventListener("olorin:pong", (e) => console.log(e.detail));
// e.detail = { id: "hello", version: "2.1.0" }
document.dispatchEvent(new CustomEvent("olorin:ping", { detail: { id: "hello" } }));
// Ask the companion app for its version and protocol
document.addEventListener("olorin:status-result", (e) => console.log(e.detail));
// e.detail = { id: "st-1", success: true, data: { version: "...", protocol: ... } }
// (companion apps before 2.0 never answer — expect { success: false, error: "Timed out ..." })
document.dispatchEvent(new CustomEvent("olorin:status", { detail: { id: "st-1" } }));
// Print an element — or pass raw HTML via 'content' instead of 'selector'
document.dispatchEvent(
new CustomEvent("olorin:print", {
detail: {
id: "job-1", // optional, echoed in the result event
printer: "receipt_printer", // one of the five logical printer keys
selector: "#receipt", // or: content: "<h1>Receipt</h1>..."
copies: 2, // optional per-job overrides, passed through verbatim:
duplex: "long", // copies, duplex, pageWidth, pageHeight, marginTop,
orientation: "Portrait", // marginRight, marginBottom, marginLeft, orientation
},
}),
);
// Open a cash drawer
document.dispatchEvent(
new CustomEvent("olorin:kick-drawer", {
detail: { id: "till-1", printer: "receipt_printer" },
}),
);
Outcomes arrive as the same olorin:print-result / olorin:drawer-result
events the button flow dispatches. When a request detail carries an id,
the result detail echoes it — dispatch concurrent requests with distinct
ids and match results by detail.id. Requests without an id produce
results without one, exactly like button clicks.
docs/KOHA_COOKBOOK.md has ready-to-paste notice
templates for a checkout quick slip and a hold slip, the
IntranetSlipPrinterJS auto-print snippet, a cash drawer button, and an
IntranetUserJS snippet that alerts staff when a print fails.
docs/DEPLOYMENT.md covers force-installing the
extension on managed workstations: Chrome’s ExtensionInstallForcelist
policy and Firefox’s policies.json ExtensionSettings.
npm install
npm test # vitest unit tests
npm run lint # eslint + prettier
npm run build # builds dist/chrome and dist/firefox + zips in web-ext-artifacts/
npm run lint:firefox # addons-linter on the built Firefox package
npm run start:firefox # run the built extension in a temporary Firefox profile
npm run check # lint + test + lint:firefox
The source lives in src/. manifest.base.json holds everything the two
browsers share; manifest.chrome.json and manifest.firefox.json are
per-browser overlays merged at build time by scripts/build.mjs, which also
injects the version from package.json (the single source of truth for the
version number).
To load a development build: chrome://extensions → Developer mode → “Load
unpacked” → dist/chrome, or use npm run start:firefox.
GPL-3.0 — see LICENSE.