libniimai 0.6.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/bluetooth.d.ts +26 -0
- package/dist/client.d.ts +8 -0
- package/dist/escpos.d.ts +12 -1
- package/dist/index.d.ts +1 -1
- package/dist/libniimai.js +156 -11
- package/dist/libniimai.js.map +1 -1
- package/dist/routed.d.ts +2 -0
- package/dist/service.d.ts +17 -0
- package/dist/thermal.d.ts +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ libniimai powers [niimai](https://niimai.app), the label designer.
|
|
|
13
13
|
| **ESC/POS receipt printers** | 58 or 80 mm, continuous or stickers with gaps | 48 or 72 mm | Bluetooth, USB | MP583, MPT-II, Xprinter XP-58IIL, PT-210, RPP02, MPT-3, RPP300 and more |
|
|
14
14
|
| **Cat printers** | 58 mm continuous, stickers with gaps | 48 mm | Bluetooth | GB01, GB02, GB03, MX05, MX06, MX10 and more |
|
|
15
15
|
|
|
16
|
-
The family is found from the device the user picks: by its Bluetooth name in `PRINTER_CATALOG`, then checked against the services it offers. A name that matches no family
|
|
16
|
+
The family is found from the device the user picks: by its Bluetooth name in `PRINTER_CATALOG`, then checked against the services it offers. A name that matches no family goes to NIIMBOT first, exactly as before. If it does not look like a NIIMBOT and misses the handshake twice, its services decide (`byServices`): the cat service makes it a cat printer, any other serial service outside NIIMBOT's a receipt printer, and a printer offering only NIIMBOT services keeps the NIIMBOT error. A type from `PRINTER_TYPES` set for unrecognised printers overrides all of this. Every family gets the same treatment:
|
|
17
17
|
|
|
18
18
|
- **Detection**: the right client and settings, picked for you.
|
|
19
19
|
- **Page handling**: each page is cropped to the head, rotated where the printer needs it, and encoded in the printer's own format.
|
|
@@ -96,6 +96,8 @@ Returns a `PrinterService`, which owns one printer connection.
|
|
|
96
96
|
| `getStatus()` | The current `PrinterStatus`. |
|
|
97
97
|
| `subscribe(fn)` | Calls `fn` on every status change. Returns a function that unsubscribes. |
|
|
98
98
|
| `feed(mm)` | Moves the paper by `mm` to line it up, back into the printer when negative. For printers that cannot sense the paper, where `status.canFeed` is true. |
|
|
99
|
+
| `rememberMatches(map)` | Printers matched by their services before, by Bluetooth name (`{ 'MTP-4B_6DEF': 'escpos' }`, built from `status.deviceName` and `status.matchedAs`). They connect straight to that client, without the NIIMBOT handshake a receipt printer would print as text. |
|
|
100
|
+
| `setPrintWidth(mm)` | Sets the print width on a printer whose head width is not known (`status.headUnknown`): a stock module name, or a printer matched by its services. Other printers keep their own. |
|
|
99
101
|
| `setManualProfile(id)` | Picks a printer type for a printer that is not recognised: a NIIMBOT model from `MANUAL_PROFILES`, or a receipt, label or cat printer from `PRINTER_TYPES`. With one of those, the Bluetooth chooser lists every device. |
|
|
100
102
|
|
|
101
103
|
**Print options**
|
|
@@ -120,6 +122,8 @@ Returns a `PrinterService`, which owns one printer connection.
|
|
|
120
122
|
| `battery` | Battery level, where the printer reports one. |
|
|
121
123
|
| `progress` | `{ page, total, sending }`; `sending` is 0–100 for the page going over the link. |
|
|
122
124
|
| `canFeed` | True when `feed(mm)` can move the paper: ESC/POS and cat printers. |
|
|
125
|
+
| `matchedAs` | `'escpos'` or `'cat'` when the printer was matched by its services; keep it for `rememberMatches`. |
|
|
126
|
+
| `headUnknown` | True when the head's width is a guess (384 dots until set) and `setPrintWidth(mm)` can set it. |
|
|
123
127
|
|
|
124
128
|
### Your own clients
|
|
125
129
|
|
package/dist/bluetooth.d.ts
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
|
+
import { type CatalogEntry } from './catalog.js';
|
|
1
2
|
import { RoutedClient } from './routed.js';
|
|
2
3
|
type Lib = typeof import('@mmote/niimbluelib');
|
|
4
|
+
/**
|
|
5
|
+
* What an unknown printer that did not answer as a NIIMBOT is, by its
|
|
6
|
+
* services: the cat service makes it a cat printer, and any other serial
|
|
7
|
+
* service outside NIIMBOT's a receipt printer. Null when it offers only
|
|
8
|
+
* NIIMBOT services, or nothing to print through: those stay NIIMBOT's.
|
|
9
|
+
*/
|
|
10
|
+
export declare function byServices(services: string[]): CatalogEntry | null;
|
|
11
|
+
/** A family a printer was matched as by its services. */
|
|
12
|
+
export type MatchedFamily = 'escpos' | 'cat';
|
|
13
|
+
/** Printers matched by their services, by Bluetooth name. */
|
|
14
|
+
export type MatchedPrinters = Record<string, MatchedFamily>;
|
|
15
|
+
/** The pause before another try, growing with each one. */
|
|
16
|
+
export declare const RETRY_MS = 250;
|
|
3
17
|
/**
|
|
4
18
|
* The Bluetooth printer behind the Connect button. One chooser lists NIIMBOT
|
|
5
19
|
* printers and the known extra thermal printers. A known cat printer offering
|
|
@@ -15,8 +29,20 @@ export declare class BluetoothPrinter extends RoutedClient {
|
|
|
15
29
|
/** The kind of printer to treat an unrecognised one as; null leaves it to NIIMBOT. */
|
|
16
30
|
private fallback;
|
|
17
31
|
unrecognisedAs(typeId: string | null): void;
|
|
32
|
+
/** Printers matched by their services before, by Bluetooth name: they skip the NIIMBOT handshake. */
|
|
33
|
+
private remembered;
|
|
34
|
+
/** What the printer connected now was matched as by its services, if it was. */
|
|
35
|
+
matchedAs: MatchedFamily | undefined;
|
|
36
|
+
rememberMatches(matches: MatchedPrinters): void;
|
|
18
37
|
connect(): Promise<{
|
|
19
38
|
deviceName?: string;
|
|
20
39
|
}>;
|
|
40
|
+
/**
|
|
41
|
+
* An unknown name that failed its first NIIMBOT try. NIIMBOT goes first
|
|
42
|
+
* again, so an unknown NIIMBOT that missed one handshake still gets its
|
|
43
|
+
* own client; only after a second miss do its services decide, and a
|
|
44
|
+
* printer offering only NIIMBOT services is never handed to another client.
|
|
45
|
+
*/
|
|
46
|
+
private unknown;
|
|
21
47
|
}
|
|
22
48
|
export {};
|
package/dist/client.d.ts
CHANGED
|
@@ -60,12 +60,20 @@ export interface ClientLike {
|
|
|
60
60
|
feedPaper?(dots: number): Promise<void>;
|
|
61
61
|
/** feedPaper can also move the paper back. */
|
|
62
62
|
feedsBack?: boolean;
|
|
63
|
+
/** The head's width is not known (a stock module name, or a printer matched by its services): `setHeadDots` sets it. */
|
|
64
|
+
headUnknown?: boolean;
|
|
65
|
+
/** Sets how many dots the head prints across, on printers whose head width is not known. */
|
|
66
|
+
setHeadDots?(dots: number): void;
|
|
63
67
|
/**
|
|
64
68
|
* Bluetooth: the kind of printer to treat one with an unknown name as, set
|
|
65
69
|
* before connecting: an id from PRINTER_TYPES, or a NIIMBOT profile id the
|
|
66
70
|
* client ignores.
|
|
67
71
|
*/
|
|
68
72
|
unrecognisedAs?(typeId: string | null): void;
|
|
73
|
+
/** Bluetooth: printers matched by their services on earlier connections, by name; they skip the NIIMBOT handshake. */
|
|
74
|
+
rememberMatches?(matches: Record<string, 'escpos' | 'cat'>): void;
|
|
75
|
+
/** Bluetooth: what the printer just connected was matched as by its services, if it was. */
|
|
76
|
+
matchedAs?: 'escpos' | 'cat';
|
|
69
77
|
}
|
|
70
78
|
/** `serial`: a printer on a USB cable that shows up as a serial port, as NIIMBOT printers do. `usb`: one that shows up as a standard USB printer. */
|
|
71
79
|
export type ConnectionKind = 'bluetooth' | 'serial' | 'usb' | 'demo';
|
package/dist/escpos.d.ts
CHANGED
|
@@ -44,10 +44,21 @@ export declare class EscPosPrinterClient extends ThermalPrinterClient {
|
|
|
44
44
|
readonly model: ThermalModel | null;
|
|
45
45
|
protected readonly densityLevels = 1;
|
|
46
46
|
protected readonly modelName: string;
|
|
47
|
-
protected
|
|
47
|
+
protected headDots: number;
|
|
48
|
+
/** A stock module name or a printer matched by its services: the head's width is a guess until set. */
|
|
49
|
+
readonly headUnknown: boolean;
|
|
48
50
|
constructor(transport: ThermalTransport, model: ThermalModel | null, pace?: Pace);
|
|
51
|
+
/** Sets the head's width, in whole bytes of dots, on a printer whose width is not known; a known head keeps its own. */
|
|
52
|
+
setHeadDots(dots: number): void;
|
|
49
53
|
protected encodeRows(data: ArrayLike<number>, width: number, height: number): Uint8Array<ArrayBufferLike>[];
|
|
50
54
|
protected job(rows: Uint8Array[], _density: number, copies: number, _labelType: number, gapDots?: number): Uint8Array<ArrayBufferLike>;
|
|
55
|
+
/**
|
|
56
|
+
* A job starts with ESC @. Some printers drop what arrives while they
|
|
57
|
+
* reset, losing the top of the label (the MTP-4B lost its first band), so
|
|
58
|
+
* the reset goes on its own and the rest follows a moment later. The bytes
|
|
59
|
+
* are the same either way.
|
|
60
|
+
*/
|
|
61
|
+
send(bytes: Uint8Array, onSent?: (share: number) => void): Promise<void>;
|
|
51
62
|
/** These printers cannot sense the paper, so the user lines it up by hand. */
|
|
52
63
|
feedPaper(dots: number): Promise<void>;
|
|
53
64
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export { effectivePaper, labelTypeOf, PAPER_TYPES, paperFromLabelType, paperName
|
|
|
29
29
|
export type { PaperType } from './paper.js';
|
|
30
30
|
export { findPrinterType, identifyPrinter, identifyUsbPrinter, PRINTER_CATALOG, PRINTER_TYPES } from './catalog.js';
|
|
31
31
|
export type { CatalogEntry, PrinterFamily, PrinterType } from './catalog.js';
|
|
32
|
-
export { BluetoothPrinter } from './bluetooth.js';
|
|
32
|
+
export { BluetoothPrinter, byServices, type MatchedFamily, type MatchedPrinters } from './bluetooth.js';
|
|
33
33
|
export { familiesFromDeviceId, parseDeviceId, rememberedUsbPrinters, UsbPrinter, usbTransport, USB_PRINTER_CLASS } from './usb.js';
|
|
34
34
|
export { describeBleError } from './ble.js';
|
|
35
35
|
export { DEMO_META, MockClient } from './mock.js';
|
package/dist/libniimai.js
CHANGED
|
@@ -198,7 +198,7 @@ function packRows(data, width, height, order, head = 384) {
|
|
|
198
198
|
}
|
|
199
199
|
return rows;
|
|
200
200
|
}
|
|
201
|
-
var sleep$
|
|
201
|
+
var sleep$2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
202
202
|
/**
|
|
203
203
|
* A thermal printer behind the same interface as a NIIMBOT client, so the
|
|
204
204
|
* printer service drives both. It reports itself through model metadata with
|
|
@@ -288,7 +288,7 @@ var ThermalPrinterClient = class {
|
|
|
288
288
|
/** Sends bytes in chunks, waiting while the printer asks for a pause. `onSent` hears the share sent, 0–1. */
|
|
289
289
|
async send(bytes, onSent) {
|
|
290
290
|
for (let i = 0; i < bytes.length;) {
|
|
291
|
-
while (this.paused) await sleep$
|
|
291
|
+
while (this.paused) await sleep$2(this.pace.pauseMs);
|
|
292
292
|
const part = bytes.subarray(i, i + this.chunk);
|
|
293
293
|
try {
|
|
294
294
|
await this.transport.write(part);
|
|
@@ -299,7 +299,7 @@ var ThermalPrinterClient = class {
|
|
|
299
299
|
}
|
|
300
300
|
i += part.length;
|
|
301
301
|
onSent?.(i / bytes.length);
|
|
302
|
-
await sleep$
|
|
302
|
+
await sleep$2(this.pace.chunkMs);
|
|
303
303
|
}
|
|
304
304
|
}
|
|
305
305
|
protocol = {
|
|
@@ -835,14 +835,22 @@ var EscPosPrinterClient = class extends ThermalPrinterClient {
|
|
|
835
835
|
densityLevels = 1;
|
|
836
836
|
modelName;
|
|
837
837
|
headDots;
|
|
838
|
+
/** A stock module name or a printer matched by its services: the head's width is a guess until set. */
|
|
839
|
+
headUnknown;
|
|
838
840
|
constructor(transport, model, pace = {
|
|
839
841
|
chunkMs: 30,
|
|
840
|
-
pauseMs: 100
|
|
842
|
+
pauseMs: 100,
|
|
843
|
+
resetMs: 300
|
|
841
844
|
}) {
|
|
842
845
|
super(transport, pace, 100);
|
|
843
846
|
this.model = model;
|
|
844
847
|
this.modelName = shownName(model, transport, "Thermal printer");
|
|
845
848
|
this.headDots = model?.headDots ?? 384;
|
|
849
|
+
this.headUnknown = !!model?.generic && model.headDots === void 0;
|
|
850
|
+
}
|
|
851
|
+
/** Sets the head's width, in whole bytes of dots, on a printer whose width is not known; a known head keeps its own. */
|
|
852
|
+
setHeadDots(dots) {
|
|
853
|
+
if (this.headUnknown) this.headDots = Math.max(8, Math.round(dots / 8) * 8);
|
|
846
854
|
}
|
|
847
855
|
encodeRows(data, width, height) {
|
|
848
856
|
return encodeRows$1(data, width, height, this.headDots);
|
|
@@ -850,6 +858,18 @@ var EscPosPrinterClient = class extends ThermalPrinterClient {
|
|
|
850
858
|
job(rows, _density, copies, _labelType, gapDots) {
|
|
851
859
|
return escposJob(rows, copies, gapDots);
|
|
852
860
|
}
|
|
861
|
+
/**
|
|
862
|
+
* A job starts with ESC @. Some printers drop what arrives while they
|
|
863
|
+
* reset, losing the top of the label (the MTP-4B lost its first band), so
|
|
864
|
+
* the reset goes on its own and the rest follows a moment later. The bytes
|
|
865
|
+
* are the same either way.
|
|
866
|
+
*/
|
|
867
|
+
async send(bytes, onSent) {
|
|
868
|
+
if (!this.pace.resetMs || bytes.length <= 2 || bytes[0] !== 27 || bytes[1] !== 64) return super.send(bytes, onSent);
|
|
869
|
+
await super.send(bytes.subarray(0, 2));
|
|
870
|
+
await sleep$2(this.pace.resetMs);
|
|
871
|
+
return super.send(bytes.subarray(2), onSent);
|
|
872
|
+
}
|
|
853
873
|
/** These printers cannot sense the paper, so the user lines it up by hand. */
|
|
854
874
|
async feedPaper(dots) {
|
|
855
875
|
if (dots < 0) throw new Error("This printer cannot move the paper back");
|
|
@@ -1176,6 +1196,12 @@ var RoutedClient = class {
|
|
|
1176
1196
|
get feedPaper() {
|
|
1177
1197
|
return this.inner?.feedPaper?.bind(this.inner);
|
|
1178
1198
|
}
|
|
1199
|
+
get headUnknown() {
|
|
1200
|
+
return this.inner?.headUnknown;
|
|
1201
|
+
}
|
|
1202
|
+
get setHeadDots() {
|
|
1203
|
+
return this.inner?.setHeadDots?.bind(this.inner);
|
|
1204
|
+
}
|
|
1179
1205
|
get feedsBack() {
|
|
1180
1206
|
return this.inner?.feedsBack;
|
|
1181
1207
|
}
|
|
@@ -1213,19 +1239,68 @@ async function thermalClient(device, server, services, { family, model }) {
|
|
|
1213
1239
|
write: TSPL_WRITE,
|
|
1214
1240
|
notify: TSPL_NOTIFY
|
|
1215
1241
|
}), model) : null;
|
|
1216
|
-
const
|
|
1242
|
+
const fixed = services.includes("000018f0-0000-1000-8000-00805f9b34fb") ? {
|
|
1217
1243
|
service: ESCPOS_SERVICE,
|
|
1218
1244
|
write: ESCPOS_WRITE
|
|
1219
1245
|
} : services.includes("e7810a71-73ae-499d-8c15-faa9aef0c3f2") ? {
|
|
1220
1246
|
service: ESCPOS_ALT_SERVICE,
|
|
1221
1247
|
write: ESCPOS_ALT_WRITE
|
|
1222
|
-
} :
|
|
1248
|
+
} : null;
|
|
1249
|
+
const transport = fixed && await webTransport(device, server, fixed).catch(() => null);
|
|
1250
|
+
if (transport) return new EscPosPrinterClient(transport, model);
|
|
1251
|
+
const channel = await writeChannel(server, services);
|
|
1223
1252
|
return channel ? new EscPosPrinterClient(await webTransport(device, server, channel), model) : null;
|
|
1224
1253
|
}
|
|
1254
|
+
/** NIIMBOT printers offer one of these; a printer with neither cannot be one. */
|
|
1255
|
+
var NIIMBOT_SERVICES = CANDIDATE_SERVICES.slice(0, 2);
|
|
1256
|
+
/** A receipt printer matched by its services: its head's width is unknown until the paper says. */
|
|
1257
|
+
var RECEIPT = {
|
|
1258
|
+
family: "escpos",
|
|
1259
|
+
model: {
|
|
1260
|
+
prefix: "",
|
|
1261
|
+
confidence: "medium",
|
|
1262
|
+
generic: true
|
|
1263
|
+
}
|
|
1264
|
+
};
|
|
1265
|
+
var CAT = findPrinterType("cat");
|
|
1266
|
+
/**
|
|
1267
|
+
* What an unknown printer that did not answer as a NIIMBOT is, by its
|
|
1268
|
+
* services: the cat service makes it a cat printer, and any other serial
|
|
1269
|
+
* service outside NIIMBOT's a receipt printer. Null when it offers only
|
|
1270
|
+
* NIIMBOT services, or nothing to print through: those stay NIIMBOT's.
|
|
1271
|
+
*/
|
|
1272
|
+
function byServices(services) {
|
|
1273
|
+
if (services.includes("0000ae30-0000-1000-8000-00805f9b34fb")) return CAT;
|
|
1274
|
+
return services.some((s) => CANDIDATE_SERVICES.includes(s) && !NIIMBOT_SERVICES.includes(s)) ? RECEIPT : null;
|
|
1275
|
+
}
|
|
1276
|
+
/**
|
|
1277
|
+
* Clears out the NIIMBOT tries a receipt printer took as text: FS . leaves
|
|
1278
|
+
* Chinese character mode, so no half character swallows what follows; two
|
|
1279
|
+
* line feeds print what is left in the buffer, even after a dangling byte;
|
|
1280
|
+
* ESC @ resets the printer.
|
|
1281
|
+
*/
|
|
1282
|
+
var FLUSH = Uint8Array.from([
|
|
1283
|
+
28,
|
|
1284
|
+
46,
|
|
1285
|
+
10,
|
|
1286
|
+
10,
|
|
1287
|
+
27,
|
|
1288
|
+
64
|
|
1289
|
+
]);
|
|
1290
|
+
var sleep$1 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
1291
|
+
/** The printer's GATT server again after a failed attempt dropped the link, giving it a moment between tries. */
|
|
1292
|
+
async function reconnect(device) {
|
|
1293
|
+
for (let i = 1; i <= 3; i++) {
|
|
1294
|
+
await sleep$1(250 * i);
|
|
1295
|
+
const server = await device.gatt?.connect().catch(() => null);
|
|
1296
|
+
if (server) return server;
|
|
1297
|
+
}
|
|
1298
|
+
return null;
|
|
1299
|
+
}
|
|
1225
1300
|
/** The name starts like one of niimbluelib's NIIMBOT models ("D110-…", "B21_…"). */
|
|
1226
1301
|
function looksNiimbot(lib, name) {
|
|
1227
1302
|
const token = name?.split(/[-_ ]/)[0].toUpperCase();
|
|
1228
|
-
return !!token && lib.modelsLibrary
|
|
1303
|
+
return !!token && !!lib.modelsLibrary?.some((m) => m.model.split("_")[0] === token);
|
|
1229
1304
|
}
|
|
1230
1305
|
/**
|
|
1231
1306
|
* The Bluetooth printer behind the Connect button. One chooser lists NIIMBOT
|
|
@@ -1247,7 +1322,15 @@ var BluetoothPrinter = class extends RoutedClient {
|
|
|
1247
1322
|
unrecognisedAs(typeId) {
|
|
1248
1323
|
this.fallback = findPrinterType(typeId);
|
|
1249
1324
|
}
|
|
1325
|
+
/** Printers matched by their services before, by Bluetooth name: they skip the NIIMBOT handshake. */
|
|
1326
|
+
remembered = {};
|
|
1327
|
+
/** What the printer connected now was matched as by its services, if it was. */
|
|
1328
|
+
matchedAs;
|
|
1329
|
+
rememberMatches(matches) {
|
|
1330
|
+
this.remembered = matches;
|
|
1331
|
+
}
|
|
1250
1332
|
async connect() {
|
|
1333
|
+
this.matchedAs = void 0;
|
|
1251
1334
|
const device = await requestPrinter(PRINTER_CATALOG.map((e) => e.model.prefix), !!this.fallback);
|
|
1252
1335
|
const known = identifyPrinter(device.name);
|
|
1253
1336
|
const fallback = !known && this.fallback && !looksNiimbot(await this.lib(), device.name) ? this.fallback : null;
|
|
@@ -1260,14 +1343,51 @@ var BluetoothPrinter = class extends RoutedClient {
|
|
|
1260
1343
|
if (!known) throw new Error(`${device.name ?? "This printer"} does not offer a ${PRINTER_TYPE_NAMES$1[entry.family]} service`);
|
|
1261
1344
|
}
|
|
1262
1345
|
const lib = await this.lib();
|
|
1346
|
+
const before = !entry && device.name && !looksNiimbot(lib, device.name) ? this.remembered[device.name] : void 0;
|
|
1347
|
+
if (before && device.gatt) {
|
|
1348
|
+
const server = await device.gatt.connect();
|
|
1349
|
+
const client = await thermalClient(device, server, (await server.getPrimaryServices().catch(() => [])).map((s) => s.uuid), before === "cat" ? CAT : RECEIPT).catch(() => null);
|
|
1350
|
+
if (client) {
|
|
1351
|
+
this.inner = client;
|
|
1352
|
+
this.matchedAs = before;
|
|
1353
|
+
return client.connect();
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1263
1356
|
this.inner = createBleClient(lib, device);
|
|
1264
1357
|
try {
|
|
1265
1358
|
return await this.inner.connect();
|
|
1266
1359
|
} catch (err) {
|
|
1267
|
-
if (looksNiimbot(lib, device.name)) throw err;
|
|
1268
|
-
|
|
1360
|
+
if (looksNiimbot(lib, device.name) || entry) throw err;
|
|
1361
|
+
return this.unknown(device, err);
|
|
1269
1362
|
}
|
|
1270
1363
|
}
|
|
1364
|
+
/**
|
|
1365
|
+
* An unknown name that failed its first NIIMBOT try. NIIMBOT goes first
|
|
1366
|
+
* again, so an unknown NIIMBOT that missed one handshake still gets its
|
|
1367
|
+
* own client; only after a second miss do its services decide, and a
|
|
1368
|
+
* printer offering only NIIMBOT services is never handed to another client.
|
|
1369
|
+
*/
|
|
1370
|
+
async unknown(device, first) {
|
|
1371
|
+
await sleep$1(250);
|
|
1372
|
+
try {
|
|
1373
|
+
return await this.inner.connect();
|
|
1374
|
+
} catch {}
|
|
1375
|
+
const server = await reconnect(device);
|
|
1376
|
+
const services = server ? (await server.getPrimaryServices().catch(() => [])).map((s) => s.uuid) : [];
|
|
1377
|
+
console.info("[niimai] services", device.name, services);
|
|
1378
|
+
const guess = byServices(services);
|
|
1379
|
+
const client = server && guess ? await thermalClient(device, server, services, guess).catch(() => null) : null;
|
|
1380
|
+
if (client) {
|
|
1381
|
+
this.inner = client;
|
|
1382
|
+
const info = await client.connect();
|
|
1383
|
+
this.matchedAs = guess.family === "cat" ? "cat" : "escpos";
|
|
1384
|
+
if (this.matchedAs === "escpos") await client.send(FLUSH).catch(() => {});
|
|
1385
|
+
return info;
|
|
1386
|
+
}
|
|
1387
|
+
console.warn("[niimai] connect failed", device.name, first);
|
|
1388
|
+
if (!services.length) throw new Error(`${device.name ?? "This printer"} offers no print service niimai knows. See Help → Printer not recognised.`);
|
|
1389
|
+
throw new Error(`Could not connect to ${device.name ?? "this printer"}. Pick its type under Advanced and connect again.`);
|
|
1390
|
+
}
|
|
1271
1391
|
};
|
|
1272
1392
|
// src/mock.ts
|
|
1273
1393
|
/** A printer that only exists in memory. Used by the demo mode and the tests. */
|
|
@@ -1685,6 +1805,16 @@ var PrinterService = class {
|
|
|
1685
1805
|
this.status = status;
|
|
1686
1806
|
this.listeners.forEach((fn) => fn(this.status));
|
|
1687
1807
|
}
|
|
1808
|
+
matches = {};
|
|
1809
|
+
/**
|
|
1810
|
+
* Printers matched by their services on earlier connections, by Bluetooth
|
|
1811
|
+
* name (from `status.matchedAs` and `status.deviceName`). They connect
|
|
1812
|
+
* straight to that client, skipping the NIIMBOT handshake, whose bytes a
|
|
1813
|
+
* receipt printer would print as text.
|
|
1814
|
+
*/
|
|
1815
|
+
rememberMatches(matches) {
|
|
1816
|
+
this.matches = matches;
|
|
1817
|
+
}
|
|
1688
1818
|
setManualProfile(id) {
|
|
1689
1819
|
this.manualProfileId = id;
|
|
1690
1820
|
if (this.client) this.set({ profile: this.currentProfile() });
|
|
@@ -1728,6 +1858,7 @@ var PrinterService = class {
|
|
|
1728
1858
|
try {
|
|
1729
1859
|
client = await this.createClient(kind);
|
|
1730
1860
|
client.unrecognisedAs?.(this.manualProfileId);
|
|
1861
|
+
client.rememberMatches?.(this.matches);
|
|
1731
1862
|
const info = await client.connect();
|
|
1732
1863
|
this.client = client;
|
|
1733
1864
|
this.set({ deviceName: info.deviceName });
|
|
@@ -1766,7 +1897,9 @@ var PrinterService = class {
|
|
|
1766
1897
|
profile: this.currentProfile(),
|
|
1767
1898
|
label: this.readRfid(),
|
|
1768
1899
|
...client.feedPaper ? { canFeed: true } : {},
|
|
1769
|
-
...client.feedPaper && client.feedsBack ? { canRetract: true } : {}
|
|
1900
|
+
...client.feedPaper && client.feedsBack ? { canRetract: true } : {},
|
|
1901
|
+
...client.headUnknown && client.setHeadDots ? { headUnknown: true } : {},
|
|
1902
|
+
...client.matchedAs ? { matchedAs: client.matchedAs } : {}
|
|
1770
1903
|
});
|
|
1771
1904
|
client.startHeartbeat();
|
|
1772
1905
|
}
|
|
@@ -1804,6 +1937,18 @@ var PrinterService = class {
|
|
|
1804
1937
|
} });
|
|
1805
1938
|
}
|
|
1806
1939
|
/**
|
|
1940
|
+
* Sets the print width, in mm, on a printer whose head width is not known
|
|
1941
|
+
* (`status.headUnknown`); the profile follows. Other printers keep theirs.
|
|
1942
|
+
*/
|
|
1943
|
+
setPrintWidth(mm) {
|
|
1944
|
+
const client = this.client;
|
|
1945
|
+
const profile = this.status.profile;
|
|
1946
|
+
if (!client?.setHeadDots || !this.status.headUnknown || !profile) return;
|
|
1947
|
+
client.setHeadDots(mmToPx(mm, profile.dpi));
|
|
1948
|
+
const next = this.currentProfile();
|
|
1949
|
+
if (next?.printheadPixels !== profile.printheadPixels) this.set({ profile: next });
|
|
1950
|
+
}
|
|
1951
|
+
/**
|
|
1807
1952
|
* Moves the paper by `mm`, out of the printer when positive and back in when
|
|
1808
1953
|
* negative, to line it up on printers that cannot sense it.
|
|
1809
1954
|
*/
|
|
@@ -2193,6 +2338,6 @@ async function createClient(kind) {
|
|
|
2193
2338
|
function createPrinter(options = {}) {
|
|
2194
2339
|
return new PrinterService(options.createClient ?? createClient, async (canvas, direction) => (await niimbot()).ImageEncoder.encodeCanvas(canvas, 0, direction));
|
|
2195
2340
|
}
|
|
2196
|
-
export { BluetoothPrinter, CAT_MODELS, CAT_UNSUPPORTED, CatPrinterClient, DEMO_META, DEMO_NAME, ESCPOS_MODELS, EscPosPrinterClient, MANUAL_PROFILES, MockClient, PAPER_TYPES, PRINTER_CATALOG, PRINTER_TYPES, PrinterService, THERMAL_DEFAULT_SIZE, THERMAL_HEAD_DOTS, THERMAL_HEAD_MM, THERMAL_SIZES, TSPL_MODELS, TSPL_SIZES, ThermalPrinterClient, TsplPrinterClient, USB_PRINTER_CLASS, UsbPrinter, catFeed, printJob as catJob, catModelFor, connectErrorTopic, correctTask, createClient, createPrinter, describeBleError, detectBrowser, detectPlatform, effectivePaper, escposFeed, escposJob, escposModelFor, familiesFromDeviceId, findManualProfile, findPrinterType, fitToPrinthead, friendlyError, identifyPrinter, identifyUsbPrinter, inkOutsideHead, labelTypeOf, modelFor, paperFromLabelType, paperName, parseDeviceId, printheadCrop, printheadMm, rememberedUsbPrinters, resolveProfile, rotateCanvas, shiftCanvas, supportIssue, supportsBluetooth, supportsSerial, supportsUsb, tsplJob, tsplModelFor, unsupportedReason, usbTransport, webTransport };
|
|
2341
|
+
export { BluetoothPrinter, CAT_MODELS, CAT_UNSUPPORTED, CatPrinterClient, DEMO_META, DEMO_NAME, ESCPOS_MODELS, EscPosPrinterClient, MANUAL_PROFILES, MockClient, PAPER_TYPES, PRINTER_CATALOG, PRINTER_TYPES, PrinterService, THERMAL_DEFAULT_SIZE, THERMAL_HEAD_DOTS, THERMAL_HEAD_MM, THERMAL_SIZES, TSPL_MODELS, TSPL_SIZES, ThermalPrinterClient, TsplPrinterClient, USB_PRINTER_CLASS, UsbPrinter, byServices, catFeed, printJob as catJob, catModelFor, connectErrorTopic, correctTask, createClient, createPrinter, describeBleError, detectBrowser, detectPlatform, effectivePaper, escposFeed, escposJob, escposModelFor, familiesFromDeviceId, findManualProfile, findPrinterType, fitToPrinthead, friendlyError, identifyPrinter, identifyUsbPrinter, inkOutsideHead, labelTypeOf, modelFor, paperFromLabelType, paperName, parseDeviceId, printheadCrop, printheadMm, rememberedUsbPrinters, resolveProfile, rotateCanvas, shiftCanvas, supportIssue, supportsBluetooth, supportsSerial, supportsUsb, tsplJob, tsplModelFor, unsupportedReason, usbTransport, webTransport };
|
|
2197
2342
|
|
|
2198
2343
|
//# sourceMappingURL=libniimai.js.map
|
package/dist/libniimai.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"libniimai.js","names":["sleep","encodeRows","encodeRows","PRINTER_TYPE_NAMES"],"sources":["../src/ble.ts","../src/thermal.ts","../src/cat.ts","../src/escpos.ts","../src/tspl.ts","../src/catalog.ts","../src/routed.ts","../src/bluetooth.ts","../src/mock.ts","../src/paper.ts","../src/profiles.ts","../src/service.ts","../src/client.ts","../src/usb.ts","../src/index.ts"],"sourcesContent":["import type { ConnectionInfo, NiimbotAbstractClient } from '@mmote/niimbluelib';\n\ntype Lib = typeof import('@mmote/niimbluelib');\n\n/** The standard NIIMBOT service, followed by serial-over-BLE services common on budget printers. */\nexport const CANDIDATE_SERVICES = [\n 'e7810a71-73ae-499d-8c15-faa9aef0c3f2', // NIIMBOT\n '0000e0ff-3c17-d293-8e48-14fe2e4da212', // NIIMBOT B21 C2B (model id 771), channel ffe1\n '0000ae30-0000-1000-8000-00805f9b34fb', // AE30 printer UART\n '0000ff00-0000-1000-8000-00805f9b34fb',\n '0000ffe0-0000-1000-8000-00805f9b34fb', // HM-10 style\n '0000fff0-0000-1000-8000-00805f9b34fb',\n '000018f0-0000-1000-8000-00805f9b34fb', // generic printer\n '49535343-fe7d-4ae5-8fa9-9fafd205e455', // Microchip transparent UART\n '6e400001-b5a3-f393-e0a9-e50e24dcca9e', // Nordic UART\n];\n\n/** Name prefixes of NIIMBOT printers; the chooser lists only these and known cat printers. */\nexport const NAME_PREFIXES = ['A', 'B', 'C', 'D', 'E', 'F', 'H', 'J', 'K', 'M', 'N', 'P', 'S', 'T', 'Z'];\n\n/** Opens the browser's printer chooser. `all` lists every device, for printers whose names niimai does not know. */\nexport function requestPrinter(extraPrefixes: string[] = [], all = false): Promise<BluetoothDevice> {\n if (all) return navigator.bluetooth.requestDevice({ acceptAllDevices: true, optionalServices: CANDIDATE_SERVICES });\n return navigator.bluetooth.requestDevice({\n filters: [...[...NAME_PREFIXES, ...extraPrefixes].map((namePrefix) => ({ namePrefix })), { services: [CANDIDATE_SERVICES[0]] }],\n optionalServices: CANDIDATE_SERVICES,\n });\n}\n\ninterface CharLike {\n uuid: string;\n properties: { notify: boolean; indicate?: boolean; write: boolean; writeWithoutResponse: boolean };\n}\n\nexport interface ServiceLike<C extends CharLike = CharLike> {\n uuid: string;\n getCharacteristics(): Promise<C[]>;\n}\n\nexport interface Channel<C extends CharLike = CharLike> {\n write: C;\n notify: C;\n}\n\n/**\n * Picks the characteristics to talk through. A single characteristic that both\n * notifies and accepts writes wins (the NIIMBOT layout); otherwise a write and\n * a notify characteristic from the same service are paired.\n */\nexport async function pickChannel<C extends CharLike>(services: ServiceLike<C>[]): Promise<Channel<C> | null> {\n const all: { service: string; chars: C[] }[] = [];\n for (const service of services) all.push({ service: service.uuid, chars: await service.getCharacteristics() });\n\n const canWrite = (c: C) => c.properties.writeWithoutResponse || c.properties.write;\n const canNotify = (c: C) => c.properties.notify || !!c.properties.indicate;\n\n for (const { chars } of all) {\n const both = chars.find((c) => c.properties.writeWithoutResponse && c.properties.notify);\n if (both) return { write: both, notify: both };\n }\n for (const { chars } of all) {\n const write = chars.find(canWrite);\n const notify = chars.find(canNotify);\n if (write && notify) return { write, notify };\n }\n return null;\n}\n\n/** Turns Chrome's terse GATT errors into something a person can act on. */\nexport function describeBleError(err: unknown, found: string[] = []): Error {\n const msg = err instanceof Error ? err.message : String(err);\n if (/no services found/i.test(msg) || (found.length === 0 && /characteristic/i.test(msg))) {\n return new Error('This printer uses an unknown Bluetooth service. See Help → Printer not recognised.');\n }\n if (/characteristic/i.test(msg)) return new Error(`No usable channel. Services: ${found.join(', ')}`);\n return err instanceof Error ? err : new Error(msg);\n}\n\n/**\n * A Web Bluetooth client that accepts more service layouts than niimbluelib's\n * own, which only knows the standard NIIMBOT service. The protocol handling\n * (packets, negotiation, heartbeat) is inherited unchanged.\n */\nexport function createBleClient(lib: Lib, chosen?: BluetoothDevice): NiimbotAbstractClient {\n class FlexibleBleClient extends lib.NiimbotAbstractClient {\n private server?: BluetoothRemoteGATTServer;\n private channel?: Channel<BluetoothRemoteGATTCharacteristic>;\n\n async connect(): Promise<ConnectionInfo> {\n await this.disconnect();\n const device = chosen ?? (await requestPrinter());\n if (!device.gatt) throw new Error('Device has no GATT server');\n\n const onDisconnect = () => {\n this.server = undefined;\n this.channel = undefined;\n this.emit('disconnect', new lib.DisconnectEvent());\n device.removeEventListener('gattserverdisconnected', onDisconnect);\n };\n device.addEventListener('gattserverdisconnected', onDisconnect);\n\n const server = await device.gatt.connect();\n let found: string[] = [];\n try {\n const services = await server.getPrimaryServices();\n found = services.map((s) => s.uuid);\n console.info('[niimai] services', found);\n const channel = await pickChannel(services);\n if (!channel) throw new Error('Suitable characteristic not found');\n console.info('[niimai] channel', channel.write.uuid, channel.notify.uuid);\n channel.notify.addEventListener('characteristicvaluechanged', (e) => {\n const value = (e.target as BluetoothRemoteGATTCharacteristic).value;\n if (value) this.processRawPacket(value);\n });\n await channel.notify.startNotifications();\n this.server = server;\n this.channel = channel;\n } catch (err) {\n server.disconnect();\n throw describeBleError(err, found);\n }\n\n await this.negotiateAndGetPrinterInfo(() => this.disconnect());\n const result: ConnectionInfo = {\n deviceName: device.name,\n result: this.printerInfo.connectResult ?? lib.ConnectResult.FirmwareErrors,\n };\n this.emit('connect', new lib.ConnectEvent(result));\n return result;\n }\n\n isConnected(): boolean {\n return !!this.server && !!this.channel;\n }\n\n async disconnect(): Promise<void> {\n this.stopHeartbeat();\n this.server?.disconnect();\n this.server = undefined;\n this.channel = undefined;\n }\n\n async sendRaw(data: Uint8Array, force?: boolean): Promise<void> {\n const send = async () => {\n const c = this.channel?.write;\n if (!c) throw new Error('Channel is closed');\n await lib.Utils.sleep(this.packetIntervalMs);\n const buf = data.slice().buffer;\n if (c.properties.writeWithoutResponse) await c.writeValueWithoutResponse(buf);\n else await c.writeValueWithResponse(buf);\n this.emit('rawpacketsent', new lib.RawPacketSentEvent(data));\n };\n if (force) await send();\n else await this.mutex.runExclusive(send);\n }\n\n getType() {\n return 'bluetooth' as const;\n }\n }\n return new FlexibleBleClient();\n}\n","/**\n * Shared ground for the extra Bluetooth thermal printers: cat printers and\n * ESC/POS mini printers (58 mm continuous paper, 384-dot head) and TSPL\n * label printers (labels with gaps, up to 80 mm). niimai's focus is NIIMBOT;\n * these clients are kept apart so NIIMBOT printing never goes through them.\n */\nimport type { PrinterModelMeta, PrintTaskName } from '@mmote/niimbluelib';\nimport type { LabelSize } from './profiles';\nimport type { ClientLike, PrintTaskLike } from './client';\n\n/** Dots across the head: 48 mm at 203 dpi. */\nexport const THERMAL_HEAD_DOTS = 384;\n/** The print width in whole millimetres, the widest a design on continuous paper can be. */\nexport const THERMAL_HEAD_MM = 48;\n\nexport type Confidence = 'tested' | 'high' | 'medium';\n\nexport interface ThermalModel {\n /** Bluetooth name prefix. */\n prefix: string;\n confidence: Confidence;\n note?: string;\n /** The prefix names a brand or a generic Bluetooth module, not a model; the printer's own name is shown. */\n generic?: boolean;\n /** Maker and model, where the Bluetooth name does not say. */\n brand?: string;\n /** Dots across the head, where it differs from the family's usual one: 576 on 80 mm receipt printers. */\n headDots?: number;\n /** USB vendor and product IDs, for printers that also connect by cable as a standard USB printer. */\n usbIds?: [vendorId: number, productId: number][];\n}\n\n/** The name a printer shows: its model, or its own Bluetooth name when the model entry is only a brand or a stock name. */\nexport const shownName = (model: ThermalModel | null, transport: { name?: string }, fallback: string): string =>\n (model && !model.generic ? model.prefix : transport.name) || model?.prefix || fallback;\n\n/** The known model a Bluetooth name belongs to, longest prefix first. */\nexport function modelFor<M extends ThermalModel>(models: M[], name: string | undefined): M | null {\n if (!name) return null;\n return [...models].sort((a, b) => b.prefix.length - a.prefix.length).find((m) => name.startsWith(m.prefix)) ?? null;\n}\n\n/** Sizes that suit 58 mm continuous paper: the full 48 mm print width at common lengths. */\nexport const THERMAL_SIZES: LabelSize[] = [15, 20, 30, 48, 80].map((heightMm) => ({ widthMm: 48, heightMm }));\nexport const THERMAL_DEFAULT_SIZE: LabelSize = { widthMm: 48, heightMm: 30 };\n\n/**\n * Packs a 1-bit page (RGBA, dark = black) into rows as wide as the head, centred across\n * the head. Cat printers want the leftmost dot in the lowest bit, ESC/POS in\n * the highest.\n */\nexport function packRows(data: ArrayLike<number>, width: number, height: number, order: 'lsb' | 'msb', head = THERMAL_HEAD_DOTS): Uint8Array[] {\n const offset = Math.max(0, Math.floor((head - width) / 2));\n const rows: Uint8Array[] = [];\n for (let y = 0; y < height; y++) {\n const row = new Uint8Array(head / 8);\n for (let x = 0; x < width && x + offset < head; x++) {\n if (data[(y * width + x) * 4] < 128) {\n const dot = x + offset;\n row[dot >> 3] |= order === 'lsb' ? 1 << (dot & 7) : 0x80 >> (dot & 7);\n }\n }\n rows.push(row);\n }\n return rows;\n}\n\n/** The Bluetooth or USB link, kept narrow so tests can stand in for it. */\nexport interface ThermalTransport {\n name?: string;\n /** Bytes per write, for links that take more than Bluetooth's cautious start. */\n chunkBytes?: number;\n write(chunk: Uint8Array): Promise<void>;\n onNotify(fn: (data: Uint8Array) => void): void;\n onDisconnect(fn: () => void): void;\n disconnect(): Promise<void>;\n}\n\nexport interface Pace {\n /** Pause after each write. */\n chunkMs: number;\n /** How often to look again while the printer asks to wait. */\n pauseMs: number;\n}\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\n/**\n * A thermal printer behind the same interface as a NIIMBOT client, so the\n * printer service drives both. It reports itself through model metadata with\n * kind \"thermal\"; it has no roll tags, battery or heartbeat to offer.\n * Subclasses supply the row encoding and the bytes for a job.\n */\nexport abstract class ThermalPrinterClient implements ClientLike {\n private listeners = new Map<string, Set<(e: unknown) => void>>();\n private connected = false;\n /** Set while the printer asks to wait (cat printers' flow control). */\n protected paused = false;\n /** Bytes per write. Starts large and halves if the link refuses it. */\n private chunk: number;\n\n protected abstract readonly densityLevels: number;\n /** The model name shown for this printer. */\n protected abstract readonly modelName: string;\n /** Dots across the head. */\n protected readonly headDots: number = THERMAL_HEAD_DOTS;\n /** \"left\": the label runs sideways through the head, as on NIIMBOT D-series printers. */\n protected readonly printDirection: 'top' | 'left' = 'top';\n /** Continuous-paper printers report \"thermal\"; label printers report nothing and get the roll UI. */\n protected readonly kind: 'thermal' | undefined = 'thermal';\n /** Label sizes common for this printer, offered in place of NIIMBOT roll sizes. */\n protected readonly labelSizes: LabelSize[] | undefined = undefined;\n protected abstract encodeRows(data: ArrayLike<number>, width: number, height: number): Uint8Array[];\n /** `gapDots`: labels with gaps on a printer that cannot sense them; each label is followed by exactly this much feed. */\n protected abstract job(rows: Uint8Array[], density: number, copies: number, labelType: number, gapDots?: number): Uint8Array;\n /** Called after each page is sent; printers that confirm a job wait here. */\n protected async afterPage(): Promise<void> {}\n\n constructor(\n protected transport: ThermalTransport,\n protected pace: Pace,\n firstChunk: number,\n ) {\n this.chunk = transport.chunkBytes ?? firstChunk;\n transport.onDisconnect(() => {\n this.connected = false;\n this.emit('disconnect', {});\n });\n }\n\n async connect() {\n this.connected = true;\n return { deviceName: this.transport.name };\n }\n\n async disconnect() {\n this.connected = false;\n await this.transport.disconnect();\n }\n\n isConnected() {\n return this.connected;\n }\n\n on(event: string, fn: (e: unknown) => void) {\n if (!this.listeners.has(event)) this.listeners.set(event, new Set());\n this.listeners.get(event)!.add(fn);\n }\n\n off(event: string, fn: (e: unknown) => void) {\n this.listeners.get(event)?.delete(fn);\n }\n\n private emit(event: string, e: unknown) {\n this.listeners.get(event)?.forEach((fn) => fn(e));\n }\n\n getPrinterInfo() {\n return {};\n }\n\n getModelMetadata(): PrinterModelMeta {\n return {\n model: this.modelName,\n ...(this.kind ? { kind: this.kind } : {}),\n ...(this.labelSizes ? { labelSizes: this.labelSizes } : {}),\n dpi: 203,\n printheadPixels: this.headDots,\n printDirection: this.printDirection,\n densityMin: 1,\n densityMax: this.densityLevels,\n densityDefault: Math.ceil(this.densityLevels / 2),\n } as unknown as PrinterModelMeta;\n }\n\n getPrintTaskType(): PrintTaskName {\n // The service needs a task name; these clients print the same way whatever it is.\n return 'B1' as PrintTaskName;\n }\n\n getRfidInfo() {\n return {};\n }\n\n async fetchRfidInfo() {}\n startHeartbeat() {}\n stopHeartbeat() {}\n\n /** Pages arrive as 1-bit canvases; the service hands them here instead of to niimbluelib's encoder. */\n encodePage(canvas: HTMLCanvasElement): Uint8Array[] {\n const ctx = canvas.getContext('2d');\n if (!ctx) throw new Error('Canvas is unavailable');\n return this.encodeRows(ctx.getImageData(0, 0, canvas.width, canvas.height).data, canvas.width, canvas.height);\n }\n\n /** Sends bytes in chunks, waiting while the printer asks for a pause. `onSent` hears the share sent, 0–1. */\n async send(bytes: Uint8Array, onSent?: (share: number) => void): Promise<void> {\n for (let i = 0; i < bytes.length; ) {\n while (this.paused) await sleep(this.pace.pauseMs);\n const part = bytes.subarray(i, i + this.chunk);\n try {\n await this.transport.write(part);\n } catch (err) {\n if (this.chunk <= 20) throw err;\n this.chunk = Math.max(20, this.chunk >> 1);\n continue;\n }\n i += part.length;\n onSent?.(i / bytes.length);\n await sleep(this.pace.chunkMs);\n }\n }\n\n protocol = {\n printEnd: async () => true,\n newPrintTask: (_name: PrintTaskName, options: Record<string, unknown>): PrintTaskLike => {\n const density = Number(options.density ?? 1);\n const labelType = Number(options.labelType ?? 1);\n const gapDots = options.gapDots === undefined ? undefined : Math.max(0, Math.round(Number(options.gapDots)));\n // A batch sends several pages to one task; progress counts across them.\n let printed = 0;\n return {\n printInit: async () => {},\n printPage: async (image, quantity = 1) => {\n const total = () => Math.max(printed, Number(options.totalPages ?? printed));\n // Sending is the slow part over Bluetooth, so report it as it goes, in whole percents.\n let shown = -1;\n await this.send(this.job(image as Uint8Array[], density, Math.max(1, quantity), labelType, gapDots), (share) => {\n const sending = Math.floor(share * 100);\n if (sending === shown) return;\n shown = sending;\n this.emit('printprogress', { page: printed, pagesTotal: total(), pagePrintProgress: 0, pageFeedProgress: 0, sending });\n });\n await this.afterPage();\n printed += Math.max(1, quantity);\n this.emit('printprogress', { page: printed, pagesTotal: total(), pagePrintProgress: 100, pageFeedProgress: 100 });\n },\n waitForFinished: async () => {},\n printEnd: async () => true,\n };\n },\n };\n}\n\n/** A Web Bluetooth link through one service: a write characteristic and, if present, a notify one. */\nexport async function webTransport(\n device: BluetoothDevice,\n server: BluetoothRemoteGATTServer,\n ids: { service: string; write: string; notify?: string },\n): Promise<ThermalTransport> {\n const service = await server.getPrimaryService(ids.service);\n const tx = await service.getCharacteristic(ids.write);\n const notify: ((d: Uint8Array) => void)[] = [];\n if (ids.notify) {\n const rx = await service.getCharacteristic(ids.notify);\n rx.addEventListener('characteristicvaluechanged', (e) => {\n const v = (e.target as BluetoothRemoteGATTCharacteristic).value;\n if (v) notify.forEach((fn) => fn(new Uint8Array(v.buffer.slice(v.byteOffset, v.byteOffset + v.byteLength))));\n });\n await rx.startNotifications();\n }\n return {\n name: device.name,\n write: async (chunk) => {\n const buf = chunk.slice().buffer;\n if (tx.properties.writeWithoutResponse) await tx.writeValueWithoutResponse(buf);\n else await tx.writeValueWithResponse(buf);\n },\n onNotify: (fn) => notify.push(fn),\n onDisconnect: (fn) => device.addEventListener('gattserverdisconnected', fn),\n disconnect: async () => server.disconnect(),\n };\n}\n","/**\n * \"Cat printers\": the small Bluetooth thermal printers sold as GB01, MX06 and\n * the like. See thermal.ts for what they share with other extras.\n *\n * Protocol, from rbaron/catprinter and NaitLee/Cat-Printer: packets are\n * 51 78 <cmd> 00 <len> 00 <payload> <crc8(payload)> FF over service AE30,\n * written to AE01. The printer sends flow control on AE02.\n */\nimport { modelFor, packRows, shownName, THERMAL_HEAD_DOTS, ThermalPrinterClient, type Confidence, type Pace, type ThermalModel, type ThermalTransport } from './thermal';\n\nexport const CAT_SERVICE = '0000ae30-0000-1000-8000-00805f9b34fb';\nexport const CAT_TX = '0000ae01-0000-1000-8000-00805f9b34fb';\nexport const CAT_RX = '0000ae02-0000-1000-8000-00805f9b34fb';\n\nconst ROW_BYTES = THERMAL_HEAD_DOTS / 8;\n\nexport type { Confidence };\n\nexport interface CatModel extends ThermalModel {\n /** Takes the newer \"start printing\" command. */\n newKind?: boolean;\n /** Mishandles the feed command, so the paper is fed with blank rows. */\n feedWithRows?: boolean;\n}\n\n/**\n * Models known to speak this protocol. \"high\": supported by both reference\n * projects and widely used. \"medium\": listed by Cat-Printer, fewer reports.\n * \"tested\": printed with niimai on real hardware.\n */\nexport const CAT_MODELS: CatModel[] = [\n { prefix: 'GB01', confidence: 'high' },\n { prefix: 'GB02', confidence: 'high' },\n { prefix: 'GB03', confidence: 'high', newKind: true },\n { prefix: 'MX05', confidence: 'high', feedWithRows: true },\n { prefix: 'MX06', confidence: 'high', feedWithRows: true },\n { prefix: 'MX10', confidence: 'high', feedWithRows: true },\n { prefix: 'MX08', confidence: 'medium', feedWithRows: true },\n { prefix: 'MX09', confidence: 'medium', feedWithRows: true },\n { prefix: 'MX11', confidence: 'medium' },\n { prefix: 'GT01', confidence: 'medium' },\n { prefix: 'YT01', confidence: 'medium' },\n { prefix: 'PD01', confidence: 'medium' },\n { prefix: 'MXTP', confidence: 'medium' },\n { prefix: 'SC03h', confidence: 'medium' },\n];\n\n/** Look-alikes that use other protocols. Listed in Help and docs/thermal-printers.md. */\nexport const CAT_UNSUPPORTED: { name: string; note: string }[] = [\n { name: 'MXW01', note: 'Newer protocol' },\n { name: 'PeriPage', note: 'Different protocol' },\n { name: 'Phomemo M02 / T02', note: 'Different protocol' },\n];\n\nexport const catModelFor = (name: string | undefined): CatModel | null => modelFor(CAT_MODELS, name);\n\n// CRC-8, polynomial 0x07, as the printers expect.\nconst CRC_TABLE = Array.from({ length: 256 }, (_, i) => {\n let c = i;\n for (let b = 0; b < 8; b++) c = c & 0x80 ? ((c << 1) ^ 0x07) & 0xff : (c << 1) & 0xff;\n return c;\n});\nexport const crc8 = (data: ArrayLike<number>): number => {\n let c = 0;\n for (let i = 0; i < data.length; i++) c = CRC_TABLE[(c ^ data[i]) & 0xff];\n return c;\n};\n\nexport function packet(cmd: number, payload: ArrayLike<number>): number[] {\n if (payload.length > 0xff) throw new Error('Payload too long');\n return [0x51, 0x78, cmd, 0x00, payload.length, 0x00, ...Array.from(payload), crc8(payload), 0xff];\n}\n\nconst u16 = (v: number) => [v & 0xff, (v >> 8) & 0xff];\n\nconst CMD = {\n retract: 0xa0,\n feed: 0xa1,\n row: 0xa2,\n state: 0xa3,\n quality: 0xa4,\n lattice: 0xa6,\n update: 0xa9,\n energy: 0xaf,\n speed: 0xbd,\n applyEnergy: 0xbe,\n} as const;\n\nconst LATTICE_START = [0xaa, 0x55, 0x17, 0x38, 0x44, 0x5f, 0x5f, 0x5f, 0x44, 0x38, 0x2c];\nconst LATTICE_END = [0xaa, 0x55, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17];\n\n/** Flow control the printer sends on AE02 when its buffer fills and drains. */\nexport const FLOW_PAUSE = [0x51, 0x78, 0xae, 0x01, 0x01, 0x00, 0x10, 0x70, 0xff];\nexport const FLOW_RESUME = [0x51, 0x78, 0xae, 0x01, 0x01, 0x00, 0x00, 0x00, 0xff];\n\n/** Heat energy for each darkness step; Cat-Printer prints images at 0x4000. */\nconst ENERGY = [0x2000, 0x3000, 0x4000, 0x6000, 0x9000];\n/** Rows fed after the last copy, so the print clears the tear bar. */\nconst FEED_AFTER = 128;\n/** Blank rows between copies. */\nconst GAP_BETWEEN = 24;\n\n/** Rows for a cat printer: leftmost dot in the lowest bit. */\nexport const encodeRows = (data: ArrayLike<number>, width: number, height: number): Uint8Array[] => packRows(data, width, height, 'lsb');\n\n/**\n * Everything sent for one job: setup, the rows for each copy, then feed and\n * finish. With `gapDots`, for labels with gaps, every label is followed by\n * exactly that feed, so the next print starts on the next label.\n */\nexport function printJob(rows: Uint8Array[], { density, copies, model, gapDots }: { density: number; copies: number; model: CatModel | null; gapDots?: number }): Uint8Array {\n const out: number[] = [];\n const push = (cmd: number, payload: ArrayLike<number>) => out.push(...packet(cmd, payload));\n push(CMD.state, [0]);\n if (model?.newKind) out.push(0x12);\n push(CMD.state, [0]);\n push(CMD.quality, [50]);\n push(CMD.speed, [32]);\n push(CMD.energy, u16(ENERGY[Math.min(ENERGY.length, Math.max(1, Math.round(density))) - 1]));\n push(CMD.applyEnergy, [1]);\n push(CMD.update, [0]);\n push(CMD.lattice, LATTICE_START);\n const blank = new Uint8Array(ROW_BYTES);\n for (let c = 0; c < copies; c++) {\n if (c > 0) for (let i = 0; i < (gapDots ?? GAP_BETWEEN); i++) push(CMD.row, blank);\n for (const row of rows) push(CMD.row, row);\n }\n push(CMD.lattice, LATTICE_END);\n push(CMD.speed, [8]);\n if (gapDots !== undefined) out.push(...catFeed(gapDots, model));\n else if (model?.feedWithRows) for (let i = 0; i < FEED_AFTER; i++) push(CMD.row, blank);\n else push(CMD.feed, u16(FEED_AFTER));\n push(CMD.state, [0]);\n return Uint8Array.from(out);\n}\n\n/** Moves the paper by `dots`, back when negative. Models that mishandle the feed command get blank rows. */\nexport function catFeed(dots: number, model: CatModel | null): Uint8Array {\n const n = Math.abs(Math.round(dots));\n if (!n) return new Uint8Array();\n if (dots < 0) return Uint8Array.from(packet(CMD.retract, u16(n)));\n if (!model?.feedWithRows) return Uint8Array.from(packet(CMD.feed, u16(n)));\n const blank = new Uint8Array(ROW_BYTES);\n return Uint8Array.from(Array.from({ length: n }, () => packet(CMD.row, blank)).flat());\n}\n\n/** A cat printer: the shared thermal client with cat packets and flow control. */\nexport class CatPrinterClient extends ThermalPrinterClient {\n protected readonly densityLevels = ENERGY.length;\n protected readonly modelName: string;\n\n constructor(\n transport: ThermalTransport,\n readonly model: CatModel | null,\n pace: Pace = { chunkMs: 20, pauseMs: 100 },\n ) {\n super(transport, pace, 180);\n this.modelName = shownName(model, transport, 'Cat printer');\n transport.onNotify((data) => {\n if (same(data, FLOW_PAUSE)) this.paused = true;\n else if (same(data, FLOW_RESUME)) this.paused = false;\n });\n }\n\n protected encodeRows(data: ArrayLike<number>, width: number, height: number) {\n return encodeRows(data, width, height);\n }\n\n protected job(rows: Uint8Array[], density: number, copies: number, _labelType: number, gapDots?: number) {\n return printJob(rows, { density, copies, model: this.model, gapDots });\n }\n\n readonly feedsBack = true;\n\n /** These printers cannot sense the paper, so the user lines it up by hand. */\n async feedPaper(dots: number): Promise<void> {\n await this.send(catFeed(dots, this.model));\n }\n}\n\nconst same = (a: ArrayLike<number>, b: number[]) => a.length === b.length && b.every((v, i) => a[i] === v);\n","/**\n * ESC/POS mini printers: 58 mm Bluetooth receipt printers such as the MP583\n * (YAEN MPT-II). They take standard raster images (GS v 0) over the 18F0\n * printer service. See thermal.ts for what they share with other extras.\n */\nimport { modelFor, packRows, shownName, THERMAL_HEAD_DOTS, ThermalPrinterClient, type Pace, type ThermalModel, type ThermalTransport } from './thermal';\n\nexport const ESCPOS_SERVICE = '000018f0-0000-1000-8000-00805f9b34fb';\nexport const ESCPOS_WRITE = '00002af1-0000-1000-8000-00805f9b34fb';\n/** The serial service many budget printers offer, Xprinter among them; NIIMBOT printers use it too. */\nexport const ESCPOS_ALT_SERVICE = 'e7810a71-73ae-499d-8c15-faa9aef0c3f2';\nexport const ESCPOS_ALT_WRITE = 'bef8d6c9-9c21-4c9e-b632-bd58c1009f9f';\n\n/**\n * Models known to take ESC/POS raster images, by Bluetooth name. \"tested\":\n * printed with niimai on real hardware. \"high\": supported by several\n * open-source printer projects. \"medium\": the name is reported by one or two\n * of them, untested here. Names from receipt-printer projects such as\n * WebBluetoothReceiptPrinter and bitbank2/Thermal_Printer; see\n * docs/thermal-printers.md for sources. Most of these printers share one\n * Bluetooth module, so its services tell nothing apart: the name decides.\n */\n// A model's prefix is the name it shows and the key its saved settings live under, so existing entries never change it.\nexport const ESCPOS_MODELS: ThermalModel[] = [\n { prefix: 'MP583', confidence: 'tested', note: 'Sold as YAEN MPT-II' },\n { prefix: 'MPT-II', confidence: 'high', note: 'Stock module name on many 58 mm printers' },\n { prefix: 'MP58', confidence: 'medium' },\n { prefix: 'MP210', confidence: 'medium' },\n { prefix: 'MTP-II', confidence: 'high', note: 'Stock module name on many 58 mm printers' },\n { prefix: 'MTP-2', confidence: 'medium' },\n { prefix: 'MPT-3', confidence: 'medium', note: '80 mm', headDots: 576 },\n { prefix: 'MTP-3', confidence: 'medium', note: '80 mm', headDots: 576 },\n { prefix: 'PT-210', confidence: 'high', brand: 'Goojprt PT-210' },\n { prefix: 'PT210', confidence: 'medium', brand: 'Goojprt PT-210' },\n { prefix: 'GOJRPT', confidence: 'medium', brand: 'Goojprt' },\n { prefix: 'Gojprt', confidence: 'medium', brand: 'Goojprt' },\n { prefix: 'PT-200', confidence: 'medium' },\n { prefix: 'RPP02', confidence: 'high', brand: 'Rongta RPP02N', note: 'Also sold as EPPOS EP58 Plus' },\n { prefix: 'RPP300', confidence: 'medium', brand: 'Rongta RPP300', note: '80 mm', headDots: 576 },\n { prefix: 'ZJ-58', confidence: 'medium', brand: 'Zjiang' },\n { prefix: 'TM-P', confidence: 'medium', brand: 'Epson TM-P series' },\n { prefix: 'XP-P323B', confidence: 'medium', brand: 'Xprinter XP-P323B', note: '80 mm', headDots: 576 },\n { prefix: 'XP-', confidence: 'medium', brand: 'other Xprinters', generic: true },\n // Many budget printers keep their Bluetooth module's stock name: the Xprinter XP-58IIL shows as \"Printer001\".\n { prefix: 'Printer', confidence: 'tested', brand: 'Xprinter XP-58IIL, HOIN, Gprinter', note: 'Stock module name', generic: true },\n { prefix: 'printer', confidence: 'medium', note: 'Stock module name', generic: true },\n { prefix: 'BlueTooth Printer', confidence: 'medium', note: 'Stock module name: Zjiang POS-5805 and others', generic: true },\n { prefix: '58Printer', confidence: 'medium', note: 'Stock module name', generic: true },\n];\n\nexport const escposModelFor = (name: string | undefined): ThermalModel | null => modelFor(ESCPOS_MODELS, name);\n\n/** Rows per raster command; small bands keep the printer's buffer from overflowing. */\nconst BAND = 24;\n/** Dots fed between copies. */\nconst GAP_DOTS = 24;\n/** Lines fed after the last copy, so the print clears the tear bar. */\nconst FEED_LINES = 4;\n\n/** Rows for an ESC/POS printer: leftmost dot in the highest bit, centred on a head `head` dots wide. */\nexport const encodeRows = (data: ArrayLike<number>, width: number, height: number, head = THERMAL_HEAD_DOTS): Uint8Array[] =>\n packRows(data, width, height, 'msb', head);\n\n/**\n * ESC @, then each copy as GS v 0 bands with a gap between, then a feed past\n * the tear bar. With `gapDots`, for labels with gaps, every label is followed\n * by exactly that feed, so the next print starts on the next label.\n */\nexport function escposJob(rows: Uint8Array[], copies: number, gapDots?: number): Uint8Array {\n const out: number[] = [0x1b, 0x40];\n const rowBytes = rows[0]?.length ?? THERMAL_HEAD_DOTS / 8;\n for (let c = 0; c < copies; c++) {\n if (c > 0) out.push(...escposFeed(gapDots ?? GAP_DOTS));\n for (let i = 0; i < rows.length; i += BAND) {\n const band = rows.slice(i, i + BAND);\n out.push(0x1d, 0x76, 0x30, 0x00, rowBytes & 0xff, rowBytes >> 8, band.length & 0xff, band.length >> 8);\n for (const row of band) out.push(...row);\n }\n }\n if (gapDots === undefined) out.push(0x1b, 0x64, FEED_LINES);\n else out.push(...escposFeed(gapDots));\n return Uint8Array.from(out);\n}\n\n/**\n * Moves the paper forward by `dots`: line feeds with the line spacing set to\n * the distance (ESC 3 n, LF), then the default spacing back (ESC 2). The\n * MP583 ignores ESC J, the usual dot feed, but takes line feeds. These\n * printers have no reverse feed.\n */\nexport function escposFeed(dots: number): Uint8Array {\n const out: number[] = [];\n for (let left = Math.round(dots); left > 0; left -= 255) out.push(0x1b, 0x33, Math.min(255, left), 0x0a);\n if (out.length) out.push(0x1b, 0x32);\n return Uint8Array.from(out);\n}\n\n/**\n * An ESC/POS printer: the shared thermal client with raster commands. These\n * printers send no flow control, so writes are paced. Heat settings vary by\n * vendor, so darkness is left to the printer.\n */\nexport class EscPosPrinterClient extends ThermalPrinterClient {\n protected readonly densityLevels = 1;\n protected readonly modelName: string;\n protected readonly headDots: number;\n\n constructor(\n transport: ThermalTransport,\n readonly model: ThermalModel | null,\n pace: Pace = { chunkMs: 30, pauseMs: 100 },\n ) {\n super(transport, pace, 100);\n this.modelName = shownName(model, transport, 'Thermal printer');\n this.headDots = model?.headDots ?? THERMAL_HEAD_DOTS;\n }\n\n protected encodeRows(data: ArrayLike<number>, width: number, height: number) {\n return encodeRows(data, width, height, this.headDots);\n }\n\n protected job(rows: Uint8Array[], _density: number, copies: number, _labelType: number, gapDots?: number) {\n return escposJob(rows, copies, gapDots);\n }\n\n /** These printers cannot sense the paper, so the user lines it up by hand. */\n async feedPaper(dots: number): Promise<void> {\n if (dots < 0) throw new Error('This printer cannot move the paper back');\n await this.send(escposFeed(dots));\n }\n}\n","/**\n * TSPL label printers: 2- and 3-inch desktop label printers that take TSC's\n * label language over the 18F0 printer service. A job is a few text commands\n * (SIZE, GAP, DENSITY, CLS) around one BITMAP of the page, then PRINT. The\n * printer finds each label's edge itself. It answers \"OK\" on 2AF0 when it has\n * taken a job, and a status byte to ESC ! ?.\n */\nimport type { LabelSize } from './profiles';\nimport { modelFor, shownName, ThermalPrinterClient, type Pace, type ThermalModel, type ThermalTransport } from './thermal';\n\nexport const TSPL_SERVICE = '000018f0-0000-1000-8000-00805f9b34fb';\nexport const TSPL_WRITE = '00002af1-0000-1000-8000-00805f9b34fb';\nexport const TSPL_NOTIFY = '00002af0-0000-1000-8000-00805f9b34fb';\n\n/** Dots across the head: 80 mm at 203 dpi. */\nexport const TSPL_HEAD_DOTS = 640;\nconst DOTS_PER_MM = 8;\n\n/**\n * Models known to take TSPL over 18F0. \"tested\": printed with niimai on real\n * hardware. \"medium\": reported by open-source printer projects, untested here.\n */\nexport const TSPL_MODELS: ThermalModel[] = [\n { prefix: 'QR380A', confidence: 'tested', note: 'Sold as CC390', usbIds: [[0x28e9, 0x0289]] },\n { prefix: 'GP-M322', confidence: 'medium', brand: 'Gprinter GP-M322', headDots: 576 },\n];\n\nexport const tsplModelFor = (name: string | undefined): ThermalModel | null => modelFor(TSPL_MODELS, name);\n\n/** Common 2- to 3-inch label sizes, width × height in mm, smallest first. */\nexport const TSPL_SIZES: LabelSize[] = [\n [40, 30],\n [50, 25],\n [50, 30],\n [50, 40],\n [57, 32],\n [60, 40],\n [50, 50],\n [70, 50],\n [75, 50],\n [80, 50],\n [60, 60],\n [80, 60],\n [80, 80],\n [75, 100],\n].map(([widthMm, heightMm]) => ({ widthMm, heightMm }));\n\n/** TSPL's darkness steps, 0 to 15; niimai offers 1 to 15. */\nconst DENSITY_LEVELS = 15;\n/** The gap between labels, for printers that look for it. */\nconst GAP_MM = 2;\n\n/** NIIMBOT label types, as the print dialog passes them. */\nconst LABEL_TYPE = { blackmark: 2, continuous: 3 } as const;\n\n/** The status query, and the bits of its answer that stop a print. */\nexport const STATUS_QUERY = [0x1b, 0x21, 0x3f];\nconst STATUS_FAULTS: [number, string][] = [\n [0x01, 'Printer cover is open'],\n [0x02, 'Paper jam'],\n [0x04, 'Out of paper'],\n [0x08, 'Out of ribbon'],\n];\n\n/** Replies that mean a job was taken: \"OK\". */\nconst OK = [0x4f, 0x4b];\n/** How long to wait for \"OK\" before asking the printer what is wrong. */\nconst ACK_TIMEOUT_MS = 20_000;\n/** How long to wait for the answer to a status query. */\nconst STATUS_TIMEOUT_MS = 2_000;\n\n/**\n * Rows for a TSPL BITMAP: as wide as the page, leftmost dot in the highest\n * bit, and 0 for a dark dot.\n */\nexport function encodeRows(data: ArrayLike<number>, width: number, height: number): Uint8Array[] {\n const bytes = Math.ceil(width / 8);\n const rows: Uint8Array[] = [];\n for (let y = 0; y < height; y++) {\n const row = new Uint8Array(bytes).fill(0xff);\n for (let x = 0; x < width; x++) if (data[(y * width + x) * 4] < 128) row[x >> 3] &= ~(0x80 >> (x & 7));\n rows.push(row);\n }\n return rows;\n}\n\nconst ascii = (s: string) => Array.from(s, (c) => c.charCodeAt(0));\nconst mm = (dots: number) => `${Math.round((dots / DOTS_PER_MM) * 10) / 10} mm`;\n\n/** How the printer finds the start of each label. */\nfunction paperCommand(labelType: number): string {\n if (labelType === LABEL_TYPE.continuous) return 'GAP 0 mm,0 mm';\n if (labelType === LABEL_TYPE.blackmark) return `BLINE ${GAP_MM} mm,0 mm`;\n return `GAP ${GAP_MM} mm,0 mm`;\n}\n\n/** One label: setup, the page as one bitmap at the top left, then print it `copies` times. */\nexport function tsplJob(rows: Uint8Array[], { density, copies, labelType }: { density: number; copies: number; labelType: number }): Uint8Array {\n const bytes = rows[0]?.length ?? 0;\n const height = rows.length;\n const head = [\n `SIZE ${mm(bytes * 8)},${mm(height)}`,\n paperCommand(labelType),\n 'DIRECTION 0',\n `DENSITY ${Math.min(DENSITY_LEVELS, Math.max(0, Math.round(density)))}`,\n 'CLS',\n ];\n const out = ascii(head.map((l) => `${l}\\r\\n`).join('') + `BITMAP 0,0,${bytes},${height},0,`);\n for (const row of rows) for (const b of row) out.push(b);\n out.push(...ascii(`\\r\\nPRINT ${Math.max(1, copies)},1\\r\\n`));\n return Uint8Array.from(out);\n}\n\n/**\n * A TSPL label printer: the shared thermal client with TSPL jobs. It shows\n * as a label printer (roll sizes, paper types). Writes are paced at the size\n * these printers' BLE modules take without dropping data.\n */\nexport class TsplPrinterClient extends ThermalPrinterClient {\n protected readonly densityLevels = DENSITY_LEVELS;\n protected readonly modelName: string;\n protected readonly headDots: number;\n protected readonly kind = undefined;\n protected readonly labelSizes = TSPL_SIZES;\n\n private oks = 0;\n private status: number | null = null;\n private wake: (() => void) | null = null;\n\n constructor(\n transport: ThermalTransport,\n readonly model: ThermalModel | null,\n pace: Pace = { chunkMs: 30, pauseMs: 100 },\n private ackTimeoutMs = ACK_TIMEOUT_MS,\n ) {\n super(transport, pace, 100);\n this.modelName = shownName(model, transport, 'Label printer');\n this.headDots = model?.headDots ?? TSPL_HEAD_DOTS;\n transport.onNotify((data) => {\n if (data.length >= 2 && data[0] === OK[0] && data[1] === OK[1]) this.oks++;\n else if (data.length === 1) this.status = data[0];\n else return;\n this.wake?.();\n });\n }\n\n protected encodeRows(data: ArrayLike<number>, width: number, height: number) {\n return encodeRows(data, width, height);\n }\n\n /** These printers cannot sense the roll; its size is remembered per printer. */\n rollKey() {\n return `printer:${this.transport.name || this.modelName}`;\n }\n\n protected job(rows: Uint8Array[], density: number, copies: number, labelType: number) {\n this.oks = 0;\n return tsplJob(rows, { density, copies, labelType });\n }\n\n /** Resolves when `done` holds or the time runs out. */\n private async until(done: () => boolean, ms: number) {\n const deadline = Date.now() + ms;\n while (!done() && Date.now() < deadline) {\n await new Promise<void>((resolve) => {\n const t = setTimeout(resolve, deadline - Date.now());\n this.wake = () => (clearTimeout(t), resolve());\n });\n }\n this.wake = null;\n }\n\n /**\n * Waits for the printer's \"OK\", so the next label never lands on a busy\n * printer. Without one, asks for its status: a fault fails the print, and a\n * printer that is merely quiet is left to carry on.\n */\n protected async afterPage() {\n await this.until(() => this.oks > 0, this.ackTimeoutMs);\n if (this.oks > 0) return;\n this.status = null;\n await this.send(Uint8Array.from(STATUS_QUERY));\n await this.until(() => this.status !== null, STATUS_TIMEOUT_MS);\n const fault = STATUS_FAULTS.find(([bit]) => (this.status ?? 0) & bit);\n if (fault) throw new Error(fault[1]);\n console.warn('[niimai] Label printer did not confirm the job');\n }\n}\n","/**\n * Every printer outside NIIMBOT that libniimai knows, by Bluetooth name, in\n * one lookup across families. A name matches one family at most, and a name\n * that matches none is known to be unknown before anything is sent to it.\n * The models themselves live with their family: ESCPOS_MODELS, TSPL_MODELS\n * and CAT_MODELS.\n */\nimport type { ClientLike } from './client';\nimport { CAT_MODELS } from './cat';\nimport { ESCPOS_MODELS, EscPosPrinterClient } from './escpos';\nimport { THERMAL_HEAD_DOTS, type Pace, type ThermalModel, type ThermalTransport } from './thermal';\nimport { TSPL_HEAD_DOTS, TSPL_MODELS, TsplPrinterClient } from './tspl';\n\nexport type PrinterFamily = 'escpos' | 'tspl' | 'cat';\n\nexport interface CatalogEntry {\n family: PrinterFamily;\n model: ThermalModel;\n}\n\nexport const PRINTER_CATALOG: CatalogEntry[] = [\n ...ESCPOS_MODELS.map((model) => ({ family: 'escpos' as const, model })),\n ...TSPL_MODELS.map((model) => ({ family: 'tspl' as const, model })),\n ...CAT_MODELS.map((model) => ({ family: 'cat' as const, model })),\n];\n\n/** The known printer a Bluetooth name belongs to, longest prefix first across every family. */\nexport function identifyPrinter(name: string | undefined): CatalogEntry | null {\n if (!name) return null;\n let best: CatalogEntry | null = null;\n for (const entry of PRINTER_CATALOG) {\n if (name.startsWith(entry.model.prefix) && entry.model.prefix.length > (best?.model.prefix.length ?? 0)) best = entry;\n }\n return best;\n}\n\n/** The known printer behind a USB device: by its vendor and product IDs, then by its product name. */\nexport function identifyUsbPrinter({ vendorId, productId, productName }: { vendorId: number; productId: number; productName?: string }): CatalogEntry | null {\n return PRINTER_CATALOG.find((e) => e.model.usbIds?.some(([v, p]) => v === vendorId && p === productId)) ?? identifyPrinter(productName);\n}\n\n/**\n * The client for a thermal printer over a cable link. Cat printers are\n * Bluetooth only, so they get null. `ackTimeoutMs`: how long a label printer\n * waits for its \"OK\" before asking for its status.\n */\nexport function cableClient({ family, model }: CatalogEntry, transport: ThermalTransport, pace: Pace, ackTimeoutMs: number): ClientLike | null {\n if (family === 'tspl') return new TsplPrinterClient(transport, model, pace, ackTimeoutMs);\n if (family === 'escpos') return new EscPosPrinterClient(transport, model, pace);\n return null;\n}\n\n/** A kind of printer to pick for one niimai does not recognise. */\nexport interface PrinterType extends CatalogEntry {\n id: string;\n name: string;\n}\n\n/** The kinds of printer an unrecognised one can be set to, alongside NIIMBOT's MANUAL_PROFILES. */\nexport const PRINTER_TYPES: PrinterType[] = [\n { id: 'escpos-58', name: 'Receipt printer, 58 mm', family: 'escpos', model: { prefix: '', confidence: 'medium', generic: true, headDots: THERMAL_HEAD_DOTS } },\n { id: 'escpos-80', name: 'Receipt printer, 80 mm', family: 'escpos', model: { prefix: '', confidence: 'medium', generic: true, headDots: 576 } },\n { id: 'tspl', name: 'Label printer (TSPL)', family: 'tspl', model: { prefix: '', confidence: 'medium', generic: true, headDots: TSPL_HEAD_DOTS } },\n { id: 'cat', name: 'Cat printer', family: 'cat', model: { prefix: '', confidence: 'medium', generic: true } },\n];\n\nexport const findPrinterType = (id: string | null | undefined): PrinterType | null => PRINTER_TYPES.find((t) => t.id === id) ?? null;\n","import type { ClientLike } from './client';\n\n/**\n * A client that picks the real client when it connects, then passes\n * everything through to it: the Bluetooth and USB printers behind the\n * Connect buttons.\n */\nexport abstract class RoutedClient implements ClientLike {\n protected inner: ClientLike | null = null;\n\n abstract connect(): Promise<{ deviceName?: string }>;\n\n protected get client(): ClientLike {\n if (!this.inner) throw new Error('Not connected');\n return this.inner;\n }\n\n async disconnect() {\n await this.inner?.disconnect();\n }\n\n isConnected() {\n return !!this.inner?.isConnected();\n }\n\n on(event: string, fn: (e: any) => void) {\n return this.client.on(event, fn);\n }\n\n off(event: string, fn: (e: any) => void) {\n return this.inner?.off(event, fn);\n }\n\n getPrinterInfo() {\n return this.inner?.getPrinterInfo() ?? {};\n }\n\n getModelMetadata() {\n return this.inner?.getModelMetadata();\n }\n\n getPrintTaskType() {\n return this.inner?.getPrintTaskType();\n }\n\n getRfidInfo() {\n return this.inner?.getRfidInfo() ?? {};\n }\n\n fetchRfidInfo() {\n return this.client.fetchRfidInfo();\n }\n\n startHeartbeat() {\n this.inner?.startHeartbeat();\n }\n\n stopHeartbeat() {\n this.inner?.stopHeartbeat();\n }\n\n isHeartbeatStarted() {\n return !!this.inner?.isHeartbeatStarted?.();\n }\n\n get protocol() {\n return this.client.protocol;\n }\n\n get encodePage() {\n return this.inner?.encodePage?.bind(this.inner);\n }\n\n get rollKey() {\n return this.inner?.rollKey?.bind(this.inner);\n }\n\n get feedPaper() {\n return this.inner?.feedPaper?.bind(this.inner);\n }\n\n get feedsBack() {\n return this.inner?.feedsBack;\n }\n}\n","import { CANDIDATE_SERVICES, createBleClient, requestPrinter } from './ble';\nimport { findPrinterType, identifyPrinter, PRINTER_CATALOG, type CatalogEntry, type PrinterFamily, type PrinterType } from './catalog';\nimport { CAT_RX, CAT_SERVICE, CAT_TX, CatPrinterClient } from './cat';\nimport { ESCPOS_ALT_SERVICE, ESCPOS_ALT_WRITE, ESCPOS_SERVICE, ESCPOS_WRITE, EscPosPrinterClient } from './escpos';\nimport { TSPL_NOTIFY, TSPL_SERVICE, TSPL_WRITE, TsplPrinterClient } from './tspl';\nimport { webTransport } from './thermal';\nimport type { ClientLike } from './client';\nimport { RoutedClient } from './routed';\n\ntype Lib = typeof import('@mmote/niimbluelib');\n\n/**\n * A writable characteristic on one of the serial services budget printers\n * offer, for ESC/POS printers on none of the known layouts.\n */\nasync function writeChannel(server: BluetoothRemoteGATTServer, services: string[]): Promise<{ service: string; write: string } | null> {\n for (const service of CANDIDATE_SERVICES) {\n if (!services.includes(service) || service === CAT_SERVICE) continue;\n const chars = await server\n .getPrimaryService(service)\n .then((s) => s.getCharacteristics())\n .catch(() => []);\n const write = chars.find((c) => c.properties.writeWithoutResponse || c.properties.write);\n if (write) return { service, write: write.uuid };\n }\n return null;\n}\n\nconst PRINTER_TYPE_NAMES: Record<PrinterFamily, string> = { escpos: 'receipt printer', tspl: 'label printer', cat: 'cat printer' };\n\n/** The client for a known or hand-picked thermal printer, over the service its family uses; null when the printer lacks it. */\nasync function thermalClient(device: BluetoothDevice, server: BluetoothRemoteGATTServer, services: string[], { family, model }: CatalogEntry): Promise<ClientLike | null> {\n if (family === 'cat') {\n return services.includes(CAT_SERVICE) ? new CatPrinterClient(await webTransport(device, server, { service: CAT_SERVICE, write: CAT_TX, notify: CAT_RX }), model) : null;\n }\n if (family === 'tspl') {\n return services.includes(TSPL_SERVICE) ? new TsplPrinterClient(await webTransport(device, server, { service: TSPL_SERVICE, write: TSPL_WRITE, notify: TSPL_NOTIFY }), model) : null;\n }\n const channel = services.includes(ESCPOS_SERVICE)\n ? { service: ESCPOS_SERVICE, write: ESCPOS_WRITE }\n : services.includes(ESCPOS_ALT_SERVICE)\n ? { service: ESCPOS_ALT_SERVICE, write: ESCPOS_ALT_WRITE }\n : await writeChannel(server, services);\n return channel ? new EscPosPrinterClient(await webTransport(device, server, channel), model) : null;\n}\n\n/** The name starts like one of niimbluelib's NIIMBOT models (\"D110-…\", \"B21_…\"). */\nfunction looksNiimbot(lib: Lib, name: string | undefined): boolean {\n const token = name?.split(/[-_ ]/)[0].toUpperCase();\n return !!token && lib.modelsLibrary.some((m) => m.model.split('_')[0] === token);\n}\n\n/**\n * The Bluetooth printer behind the Connect button. One chooser lists NIIMBOT\n * printers and the known extra thermal printers. A known cat printer offering\n * AE30 gets the cat client; a known ESC/POS printer the ESC/POS client, over\n * 18F0, the shared E781 service (as Xprinters use), or any serial service\n * with a writable characteristic; a known TSPL label printer offering 18F0\n * the TSPL client; and everything else the NIIMBOT client exactly as before.\n * Known names never overlap, so each printer matches one client at most.\n */\nexport class BluetoothPrinter extends RoutedClient {\n constructor(private lib: () => Promise<Lib>) {\n super();\n }\n\n /** The kind of printer to treat an unrecognised one as; null leaves it to NIIMBOT. */\n private fallback: PrinterType | null = null;\n\n unrecognisedAs(typeId: string | null) {\n this.fallback = findPrinterType(typeId);\n }\n\n async connect() {\n // With a type picked for unrecognised printers, the chooser lists every device, whatever its name.\n const device = await requestPrinter(\n PRINTER_CATALOG.map((e) => e.model.prefix),\n !!this.fallback,\n );\n const known = identifyPrinter(device.name);\n // A hand-picked type is only for printers that are neither known nor named like a NIIMBOT: a NIIMBOT always keeps its own client.\n const fallback = !known && this.fallback && !looksNiimbot(await this.lib(), device.name) ? this.fallback : null;\n const entry = known ?? fallback;\n if (entry && device.gatt) {\n const server = await device.gatt.connect();\n const services = (await server.getPrimaryServices().catch(() => [])).map((s) => s.uuid);\n this.inner = await thermalClient(device, server, services, entry);\n if (this.inner) return this.inner.connect();\n // A printer set to a type by hand that lacks its service should say so, not fall to NIIMBOT.\n if (!known) throw new Error(`${device.name ?? 'This printer'} does not offer a ${PRINTER_TYPE_NAMES[entry.family]} service`);\n }\n const lib = await this.lib();\n this.inner = createBleClient(lib, device) as unknown as ClientLike;\n try {\n return await this.inner.connect();\n } catch (err) {\n if (looksNiimbot(lib, device.name)) throw err;\n throw new Error(`${device.name ?? 'This printer'} did not answer as a NIIMBOT. If it is another kind, pick its type under Advanced and connect again.`);\n }\n }\n}\n","import type { PrinterModelMeta } from '@mmote/niimbluelib';\nimport type { ClientLike, PrintTaskLike } from './client';\n\ntype Listener = (e: any) => void;\n\n/** A printer that only exists in memory. Used by the demo mode and the tests. */\nexport class MockClient implements ClientLike {\n connected = false;\n printed: { image: unknown; quantity: number; heartbeat: boolean }[] = [];\n failNextPrint: string | null = null;\n private listeners = new Map<string, Set<Listener>>();\n\n /** Pass null to act as a model the library does not recognise. */\n constructor(private meta: PrinterModelMeta | null = DEMO_META) {}\n\n async connect() {\n this.connected = true;\n this.emit('connect', { info: { deviceName: 'Demo printer' } });\n return { deviceName: 'Demo printer' };\n }\n async disconnect() {\n this.connected = false;\n this.emit('disconnect', {});\n }\n isConnected() {\n return this.connected;\n }\n on(event: string, fn: Listener) {\n if (!this.listeners.has(event)) this.listeners.set(event, new Set());\n this.listeners.get(event)!.add(fn);\n return this;\n }\n off(event: string, fn: Listener) {\n this.listeners.get(event)?.delete(fn);\n return this;\n }\n emit(event: string, e: unknown) {\n this.listeners.get(event)?.forEach((fn) => fn(e));\n }\n protocolVersion = 1;\n getPrinterInfo() {\n return { batteryPercents: 80, modelId: 512, printheadWidth: 96, protocolVersion: this.protocolVersion };\n }\n getModelMetadata() {\n return this.meta ?? undefined;\n }\n getPrintTaskType() {\n return this.meta ? ('D11_V1' as const) : undefined;\n }\n /** Set false to act like printers that report only the roll's barcode. */\n reportsPaperSize = true;\n\n getRfidInfo() {\n return {\n labelRfidInfo: { tagPresent: true, barCode: '6971501227941', allPaper: 210, usedPaper: 12, consumablesType: 1 },\n paperInfo: this.reportsPaperSize ? { valid: true, paperWidth: 40, paperHeight: 12 } : undefined,\n };\n }\n async fetchRfidInfo() {\n return this.getRfidInfo();\n }\n startHeartbeat() {\n this.heartbeat = true;\n this.emit('heartbeat', { data: { batteryPercents: 80, lidClosed: true, paperInserted: true } });\n }\n heartbeat = false;\n stopHeartbeat() {\n this.heartbeat = false;\n }\n /** How many \"print end\" polls answer false before the printer reports done. */\n printEndPending = 0;\n protocol = {\n printEnd: async () => {\n if (this.printEndPending > 0) {\n this.printEndPending--;\n return false;\n }\n return true;\n },\n newPrintTask: (_name: string, options: Record<string, unknown>): PrintTaskLike => {\n const total = Number(options.totalPages ?? 1);\n return {\n printInit: async () => {\n if (this.failNextPrint) {\n const msg = this.failNextPrint;\n this.failNextPrint = null;\n throw new Error(msg);\n }\n },\n printPage: async (image, quantity = 1) => {\n this.printed.push({ image, quantity, heartbeat: this.heartbeat });\n },\n waitForFinished: async () => {\n for (let page = 1; page <= total; page++) {\n this.emit('printprogress', { page, pagesTotal: total, pagePrintProgress: 100, pageFeedProgress: 100 });\n }\n },\n printEnd: async () => true,\n };\n },\n };\n}\n\nexport const DEMO_META = {\n model: 'D11',\n id: [512],\n dpi: 203,\n printDirection: 'left',\n printheadPixels: 96,\n paperTypes: [1],\n densityMin: 1,\n densityMax: 3,\n densityDefault: 2,\n} as unknown as PrinterModelMeta;\n","/**\n * Paper types, as niimbluelib's LabelType numbers. The printer uses this to\n * find the start of each label, so the wrong one mis-feeds the roll.\n */\nexport type PaperType = 'gaps' | 'blackmark' | 'continuous' | 'perforated' | 'transparent';\n\nexport const PAPER_TYPES: { value: PaperType; label: string; labelType: number }[] = [\n { value: 'gaps', label: 'Labels with gaps', labelType: 1 },\n { value: 'blackmark', label: 'Black mark', labelType: 2 },\n { value: 'continuous', label: 'Continuous', labelType: 3 },\n { value: 'perforated', label: 'Perforated', labelType: 4 },\n { value: 'transparent', label: 'Transparent', labelType: 5 },\n];\n\nexport const paperFromLabelType = (n: number | undefined): PaperType | undefined => PAPER_TYPES.find((p) => p.labelType === n)?.value;\n\nexport const labelTypeOf = (p: PaperType): number => PAPER_TYPES.find((x) => x.value === p)!.labelType;\n\nexport const paperName = (p: PaperType): string => PAPER_TYPES.find((x) => x.value === p)!.label;\n\n/** The paper to print on: the label's own choice, then what the roll reports, then labels with gaps. */\nexport const effectivePaper = (chosen: PaperType | undefined, fromRoll: PaperType | undefined): PaperType => chosen ?? fromRoll ?? 'gaps';\n","import type { PrinterModelMeta, PrintTaskName } from '@mmote/niimbluelib';\n\n/** A label's size in millimetres. */\nexport interface LabelSize {\n widthMm: number;\n heightMm: number;\n}\n\nexport type PrintDirection = 'left' | 'top';\n\n/** Everything the app needs to know to render and send a page to a printer. */\nexport interface PrinterProfile {\n id: string;\n name: string;\n dpi: number;\n printheadPixels: number;\n printDirection: PrintDirection;\n printTask: PrintTaskName;\n densityMin: number;\n densityMax: number;\n densityDefault: number;\n /** Extra thermal printers (cat, ESC/POS): continuous paper, no roll tags. Unset for NIIMBOT. */\n kind?: 'thermal';\n /** Label sizes common for this printer (TSPL label printers). Unset for NIIMBOT, whose sizes come from its rolls. */\n sizes?: LabelSize[];\n}\n\n/** Manual profiles for printers niimbluelib does not recognise yet. Each mirrors a known model family. */\nexport const MANUAL_PROFILES: PrinterProfile[] = [\n { id: 'd11', name: 'D11 family', dpi: 203, printheadPixels: 96, printDirection: 'left', printTask: 'D11_V1', densityMin: 1, densityMax: 3, densityDefault: 2 },\n { id: 'd110', name: 'D110 family', dpi: 203, printheadPixels: 96, printDirection: 'left', printTask: 'D110', densityMin: 1, densityMax: 3, densityDefault: 2 },\n { id: 'b1', name: 'B1 family', dpi: 203, printheadPixels: 384, printDirection: 'top', printTask: 'B1', densityMin: 1, densityMax: 5, densityDefault: 3 },\n { id: 'b21', name: 'B21 family', dpi: 203, printheadPixels: 384, printDirection: 'top', printTask: 'B21_V1', densityMin: 1, densityMax: 5, densityDefault: 3 },\n];\n\nexport const findManualProfile = (id: string | null | undefined): PrinterProfile | undefined =>\n MANUAL_PROFILES.find((p) => p.id === id);\n\n/**\n * Print tasks verified on hardware where niimbluelib's choice prints blank.\n * A B21 C2B on protocol 0 needs the original B21 packet formats; the library\n * gives every B21 C2B the B1 task, which that firmware accepts but never prints.\n */\nconst TASK_QUIRKS: { model: string; maxProtocol: number; task: PrintTaskName }[] = [{ model: 'B21_C2B', maxProtocol: 0, task: 'B21_V1' }];\n\nexport function correctTask(model: string, protocolVersion: number | undefined, task: PrintTaskName | undefined): PrintTaskName | undefined {\n const quirk = TASK_QUIRKS.find((q) => q.model === model && (protocolVersion ?? 0) <= q.maxProtocol);\n return quirk?.task ?? task;\n}\n\n/**\n * The profile to print with: the library's model data when it recognises the\n * printer, otherwise the manual choice. A detected model always wins, so a\n * choice left over from another printer cannot send the wrong settings.\n */\nexport function resolveProfile(\n meta: PrinterModelMeta | undefined,\n libraryTask: PrintTaskName | undefined,\n manual?: PrinterProfile,\n protocolVersion?: number,\n): PrinterProfile | null {\n const task = meta ? correctTask(meta.model, protocolVersion, libraryTask) : undefined;\n if (!meta || !task) return manual ?? null;\n return {\n id: `auto:${meta.model}`,\n name: meta.model.replace(/_/g, ' '),\n dpi: meta.dpi,\n printheadPixels: meta.printheadPixels,\n printDirection: meta.printDirection,\n printTask: task,\n densityMin: meta.densityMin,\n densityMax: meta.densityMax,\n densityDefault: meta.densityDefault,\n ...((meta as { kind?: string }).kind === 'thermal' ? { kind: 'thermal' as const } : {}),\n ...((meta as { labelSizes?: LabelSize[] }).labelSizes ? { sizes: (meta as { labelSizes?: LabelSize[] }).labelSizes } : {}),\n };\n}\n\n/** Widest label edge the printhead covers, in millimetres. */\nexport const printheadMm = (p: PrinterProfile): number => (p.printheadPixels / p.dpi) * 25.4;\n\n/**\n * The part of a page the printhead can print, centred across the head. Pages\n * wider than the head are cropped: printers discard rows longer than the head.\n */\nexport function printheadCrop(width: number, height: number, p: Pick<PrinterProfile, 'printheadPixels' | 'printDirection'>) {\n const acrossIsWidth = p.printDirection === 'top';\n const across = acrossIsWidth ? width : height;\n const keep = Math.min(across, p.printheadPixels);\n const offset = Math.floor((across - keep) / 2);\n return acrossIsWidth\n ? { x: offset, y: 0, width: keep, height, cropped: keep < across }\n : { x: 0, y: offset, width, height: keep, cropped: keep < across };\n}\n\n/**\n * True when any black pixel of a page (RGBA, as from getImageData) falls\n * outside what the printhead covers, so part of the design would not print.\n */\nexport function inkOutsideHead(data: ArrayLike<number>, width: number, height: number, p: Pick<PrinterProfile, 'printheadPixels' | 'printDirection'>): boolean {\n const crop = printheadCrop(width, height, p);\n if (!crop.cropped) return false;\n for (let y = 0; y < height; y++) {\n const insideRow = y >= crop.y && y < crop.y + crop.height;\n for (let x = 0; x < width; x++) {\n if (insideRow && x >= crop.x && x < crop.x + crop.width) {\n x = crop.x + crop.width - 1;\n continue;\n }\n if (data[(y * width + x) * 4] < 128) return true;\n }\n }\n return false;\n}\n","import type { ClientLike, ConnectionKind, PrintTaskLike } from './client';\nimport { paperFromLabelType, type PaperType } from './paper';\nimport { findManualProfile, printheadCrop, resolveProfile, type PrinterProfile } from './profiles';\n\nexport type PrinterState = 'disconnected' | 'connecting' | 'ready' | 'printing';\n\nexport interface PrinterStatus {\n state: PrinterState;\n deviceName?: string;\n battery?: number;\n /** Null when the printer is unknown and no manual profile is chosen. */\n profile: PrinterProfile | null;\n detectedModel?: string;\n /**\n * The loaded roll. Size comes from the printer's paper info where it reports\n * one; the barcode identifies the roll when it does not (the B21 C2B, for one).\n */\n label?: { widthMm?: number; heightMm?: number; remaining?: number; capacity?: number; barcode?: string; paper?: PaperType; manual?: boolean };\n lidClosed?: boolean;\n paperInserted?: boolean;\n /** Pages done of the total; `sending` is how much of the current page has gone over the link, 0–100, for printers that report it. */\n progress?: { page: number; total: number; sending?: number };\n /** The paper can be moved by hand with `feed()`, on printers that cannot sense it. */\n canFeed?: boolean;\n /** `feed()` can also move the paper back. */\n canRetract?: boolean;\n}\n\n/** Nudges the printed image, in mm: positive x moves right, positive y moves down. */\nexport interface PrintOffset {\n x: number;\n y: number;\n}\n\nexport interface PrintOptions {\n density: number;\n /** Copies of each page. */\n copies: number;\n labelType?: number;\n offset?: PrintOffset;\n /**\n * Labels with gaps on a printer that cannot sense them (`status.canFeed`):\n * the gap between labels, in mm. Each label is followed by exactly this\n * much feed in place of the tear-off feed, so prints stay on the labels.\n */\n gapMm?: number;\n /**\n * Turns each page clockwise by this many degrees before it is sent, for\n * labels designed landscape: the page is drawn the way it reads and prints\n * turned onto the paper. Applied before `offset`, which stays relative to the paper.\n */\n rotate?: Rotation;\n}\n\n/** Quarter turns, in degrees clockwise. */\nexport type Rotation = 0 | 90 | 180 | 270;\n\n/** Different labels in one job, drawn one at a time so a long batch never holds every page at once. */\nexport interface PageSource {\n count: number;\n page: (index: number) => Promise<HTMLCanvasElement>;\n}\n\ntype Encoder = (canvas: HTMLCanvasElement, direction: PrinterProfile['printDirection']) => unknown | Promise<unknown>;\nexport type ClientFactory = (kind: ConnectionKind) => Promise<ClientLike>;\n\nconst DISCONNECTED: PrinterStatus = { state: 'disconnected', profile: null };\n\n/** What the demo printer is called wherever a printer's name shows. */\nexport const DEMO_NAME = 'Demo printer';\n\nconst PRINT_TIMEOUT_MS = 20_000;\nconst PAGE_TIMEOUT_MS = 10_000;\nconst PRINT_END_POLL_MS = 300;\nconst mmToPx = (mm: number, dpi: number): number => (mm / 25.4) * dpi;\n\n/** The page moved by whole pixels on a white canvas of the same size. */\nexport function shiftCanvas(canvas: HTMLCanvasElement, dx: number, dy: number): HTMLCanvasElement {\n dx = Math.round(dx);\n dy = Math.round(dy);\n if (!dx && !dy) return canvas;\n const out = document.createElement('canvas');\n out.width = canvas.width;\n out.height = canvas.height;\n const ctx = out.getContext('2d');\n if (!ctx) return canvas;\n ctx.fillStyle = '#fff';\n ctx.fillRect(0, 0, out.width, out.height);\n ctx.drawImage(canvas, dx, dy);\n return out;\n}\n\n/** The page turned clockwise by `degrees`, a multiple of 90. Returns the canvas itself for no turn. */\nexport function rotateCanvas(canvas: HTMLCanvasElement, degrees: number): HTMLCanvasElement {\n const turn = (((Math.round(degrees / 90) % 4) + 4) % 4) as 0 | 1 | 2 | 3;\n if (!turn) return canvas;\n const out = document.createElement('canvas');\n out.width = turn % 2 ? canvas.height : canvas.width;\n out.height = turn % 2 ? canvas.width : canvas.height;\n const ctx = out.getContext('2d');\n if (!ctx) return canvas;\n ctx.fillStyle = '#fff';\n ctx.fillRect(0, 0, out.width, out.height);\n ctx.translate(out.width / 2, out.height / 2);\n ctx.rotate((turn * Math.PI) / 2);\n ctx.drawImage(canvas, -canvas.width / 2, -canvas.height / 2);\n return out;\n}\n\n/** Crops the page to what the printhead covers. Returns the canvas itself when it already fits. */\nexport function fitToPrinthead(canvas: HTMLCanvasElement, profile: PrinterProfile): HTMLCanvasElement {\n const crop = printheadCrop(canvas.width, canvas.height, profile);\n if (!crop.cropped) return canvas;\n const out = document.createElement('canvas');\n out.width = crop.width;\n out.height = crop.height;\n const ctx = out.getContext('2d');\n if (!ctx) return canvas;\n ctx.drawImage(canvas, crop.x, crop.y, crop.width, crop.height, 0, 0, crop.width, crop.height);\n return out;\n}\n\n/** Black pixels on the page, for the print log. A zero here means the page itself was blank. */\nfunction inkPixels(canvas: HTMLCanvasElement): number | null {\n const ctx = canvas.getContext('2d');\n if (!ctx) return null;\n const { data } = ctx.getImageData(0, 0, canvas.width, canvas.height);\n let n = 0;\n for (let i = 0; i < data.length; i += 4) if (data[i] < 128) n++;\n return n;\n}\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\nfunction withTimeout<T>(p: Promise<T>, ms: number): Promise<T> {\n return new Promise((resolve, reject) => {\n const t = setTimeout(() => reject(new Error('Timeout waiting for the printer to finish')), ms);\n p.then(\n (v) => (clearTimeout(t), resolve(v)),\n (e) => (clearTimeout(t), reject(e)),\n );\n });\n}\n\n/** Translates the library's error codes and messages into short, readable ones. */\nexport function friendlyError(err: unknown): string {\n const msg = err instanceof Error ? err.message : String(err);\n if (/user cancelled|no (device|port) selected|chooser/i.test(msg)) return 'Connection cancelled';\n if (/timeout waiting for the printer to finish/i.test(msg)) return 'The printer did not confirm the print';\n if (/timeout/i.test(msg)) return 'Printer stopped responding';\n if (/lid|cover/i.test(msg)) return 'Close the printer lid';\n if (/paper|label/i.test(msg) && /no|out|missing/i.test(msg)) return 'Load a label roll';\n if (/unknown Bluetooth service|No usable channel/i.test(msg)) return msg;\n if (/disconnected|gatt/i.test(msg)) return 'Printer disconnected';\n return msg || 'Something went wrong';\n}\n\n/**\n * Owns the printer connection. The UI subscribes to status updates and calls\n * connect/print; the raw protocol stays inside niimbluelib.\n */\nexport class PrinterService {\n private client: ClientLike | null = null;\n private status: PrinterStatus = DISCONNECTED;\n private listeners = new Set<(s: PrinterStatus) => void>();\n private manualProfileId: string | null = null;\n private detach: (() => void) | null = null;\n /** Paper moves run one after another, and a print waits for them. */\n private feeding: Promise<void> = Promise.resolve();\n\n /** How the current printer is connected. */\n private kind: ConnectionKind | null = null;\n\n constructor(\n private createClient: ClientFactory,\n private encode: Encoder,\n ) {}\n\n getStatus(): PrinterStatus {\n return this.status;\n }\n\n subscribe(fn: (s: PrinterStatus) => void): () => void {\n this.listeners.add(fn);\n return () => this.listeners.delete(fn);\n }\n\n private set(patch: Partial<PrinterStatus>): void {\n this.replace({ ...this.status, ...patch });\n }\n\n /** Swaps the whole status, so fields from an earlier connection never linger. */\n private replace(status: PrinterStatus): void {\n this.status = status;\n this.listeners.forEach((fn) => fn(this.status));\n }\n\n setManualProfile(id: string | null): void {\n this.manualProfileId = id;\n if (this.client) this.set({ profile: this.currentProfile() });\n }\n\n private currentProfile(): PrinterProfile | null {\n const c = this.client;\n const profile = resolveProfile(c?.getModelMetadata(), c?.getPrintTaskType(), findManualProfile(this.manualProfileId), c?.getPrinterInfo().protocolVersion);\n // Whatever printer stands in for the demo, it never passes for a real model.\n return profile && this.kind === 'demo' ? { ...profile, name: DEMO_NAME } : profile;\n }\n\n private readRfid(): PrinterStatus['label'] {\n // Printers that cannot sense the roll keep the size the user sets, under a key of their own.\n const manual = this.client?.rollKey?.();\n if (manual) return { barcode: manual, manual: true };\n const info = this.client?.getRfidInfo();\n if (!info) return undefined;\n const paper = info.paperInfo?.valid ? info.paperInfo : undefined;\n const tag = info.labelRfidInfo?.tagPresent ? info.labelRfidInfo : undefined;\n if (!paper && !tag) return undefined;\n return {\n widthMm: paper?.paperWidth,\n heightMm: paper?.paperHeight,\n remaining: tag && tag.allPaper !== undefined && tag.allPaper >= 0 ? Math.max(0, tag.allPaper - (tag.usedPaper ?? 0)) : undefined,\n capacity: tag && tag.allPaper !== undefined && tag.allPaper > 0 ? tag.allPaper : undefined,\n barcode: tag?.barCode || undefined,\n paper: paperFromLabelType(tag?.consumablesType ?? paper?.paperType),\n };\n }\n\n async connect(kind: ConnectionKind): Promise<void> {\n if (this.client) await this.disconnect();\n this.kind = kind;\n this.replace({ ...DISCONNECTED, state: 'connecting' });\n let client: ClientLike;\n try {\n client = await this.createClient(kind);\n client.unrecognisedAs?.(this.manualProfileId);\n const info = await client.connect();\n this.client = client;\n this.set({ deviceName: info.deviceName });\n } catch (err) {\n this.client = null;\n this.replace(DISCONNECTED);\n throw new Error(friendlyError(err));\n }\n\n const onDisconnect = () => this.handleDisconnect();\n const onHeartbeat = (e: { data: { batteryPercents?: number; lidClosed?: boolean; paperInserted?: boolean } }) =>\n this.set({ battery: e.data.batteryPercents ?? this.status.battery, lidClosed: e.data.lidClosed, paperInserted: e.data.paperInserted });\n const onRfid = () => this.set({ label: this.readRfid() });\n const onProgress = (e: { page: number; pagesTotal: number; sending?: number }) =>\n this.set({ progress: { page: e.page, total: e.pagesTotal, ...(e.sending !== undefined ? { sending: e.sending } : {}) } });\n client.on('disconnect', onDisconnect);\n client.on('heartbeat', onHeartbeat);\n client.on('rfidinfofetched', onRfid);\n client.on('printprogress', onProgress);\n this.detach = () => {\n client.off('disconnect', onDisconnect);\n client.off('heartbeat', onHeartbeat);\n client.off('rfidinfofetched', onRfid);\n client.off('printprogress', onProgress);\n };\n\n const meta = client.getModelMetadata();\n this.set({\n state: 'ready',\n battery: client.getPrinterInfo().batteryPercents,\n detectedModel: meta?.model,\n profile: this.currentProfile(),\n label: this.readRfid(),\n ...(client.feedPaper ? { canFeed: true } : {}),\n ...(client.feedPaper && client.feedsBack ? { canRetract: true } : {}),\n });\n client.startHeartbeat();\n }\n\n private handleDisconnect(): void {\n this.detach?.();\n this.detach = null;\n this.client = null;\n this.replace(DISCONNECTED);\n }\n\n async disconnect(): Promise<void> {\n const c = this.client;\n if (!c) return;\n c.stopHeartbeat();\n try {\n await c.disconnect();\n } finally {\n this.handleDisconnect();\n }\n }\n\n /**\n * B21_V1 finishes by polling \"print end\", but niimbluelib's poll cancels its\n * own timer on the first reply, so it never resolves unless the printer is\n * already done. That task polls here instead.\n */\n private async waitForFinished(client: ClientLike, task: PrintTaskLike, name: string, copies: number): Promise<void> {\n if (name !== 'B21_V1') return task.waitForFinished();\n this.set({ progress: { page: 0, total: copies } });\n while (!(await client.protocol.printEnd())) await sleep(PRINT_END_POLL_MS);\n this.set({ progress: { page: copies, total: copies } });\n }\n\n /**\n * Moves the paper by `mm`, out of the printer when positive and back in when\n * negative, to line it up on printers that cannot sense it.\n */\n feed(mm: number): Promise<void> {\n const client = this.client;\n const profile = this.status.profile;\n if (!client || !profile) return Promise.reject(new Error('Connect a printer first'));\n if (!client.feedPaper) return Promise.reject(new Error('This printer finds the paper itself'));\n if (mm < 0 && !client.feedsBack) return Promise.reject(new Error('This printer cannot move the paper back'));\n if (this.status.state === 'printing') return Promise.reject(new Error('Wait for the print to finish'));\n const dots = Math.round(mmToPx(mm, profile.dpi));\n const run = this.feeding.then(() => (dots ? client.feedPaper!(dots) : undefined));\n this.feeding = run.catch(() => {});\n return run.catch((err) => {\n throw new Error(friendlyError(err));\n });\n }\n\n async print(source: HTMLCanvasElement | PageSource, opts: PrintOptions): Promise<void> {\n const client = this.client;\n const profile = this.status.profile;\n if (!client) throw new Error('Connect a printer first');\n if (!profile) throw new Error('Choose your printer type');\n if (this.status.state === 'printing') throw new Error('Already printing');\n await this.feeding;\n\n const pages: PageSource = 'page' in source ? source : { count: 1, page: async () => source };\n const copies = Math.max(1, Math.floor(opts.copies));\n const total = copies * Math.max(1, pages.count);\n const density = Math.min(profile.densityMax, Math.max(profile.densityMin, Math.round(opts.density)));\n const task = client.protocol.newPrintTask(profile.printTask, {\n totalPages: total,\n density,\n labelType: opts.labelType ?? 1,\n ...(opts.gapMm !== undefined && client.feedPaper ? { gapDots: Math.round(mmToPx(Math.max(0, opts.gapMm), profile.dpi)) } : {}),\n pageColor: 0,\n statusPollIntervalMs: 100,\n statusTimeoutMs: 8000,\n });\n\n // Heartbeat queries interleaved with page data spoil prints on some models.\n client.stopHeartbeat();\n this.set({ state: 'printing', progress: { page: 0, total } });\n try {\n const offset = opts.offset ?? { x: 0, y: 0 };\n for (let i = 0; i < pages.count; i++) {\n const canvas = rotateCanvas(await pages.page(i), opts.rotate ?? 0);\n const shifted = shiftCanvas(canvas, mmToPx(offset.x, profile.dpi), mmToPx(offset.y, profile.dpi));\n const page = fitToPrinthead(shifted, profile);\n const image = client.encodePage ? await client.encodePage(page) : await this.encode(page, profile.printDirection);\n if (i === 0) {\n console.info('[niimai] print', {\n profile: profile.id,\n task: profile.printTask,\n direction: profile.printDirection,\n printhead: profile.printheadPixels,\n page: `${canvas.width}x${canvas.height}`,\n rotate: opts.rotate ?? 0,\n sent: `${page.width}x${page.height}`,\n density,\n copies,\n pages: pages.count,\n ink: inkPixels(page),\n });\n await task.printInit();\n }\n await task.printPage(image, copies);\n }\n await withTimeout(this.waitForFinished(client, task, profile.printTask, total), PRINT_TIMEOUT_MS + total * PAGE_TIMEOUT_MS);\n } catch (err) {\n throw new Error(friendlyError(err));\n } finally {\n try {\n await task.printEnd();\n } catch {\n // The page already failed; the original error is the useful one.\n }\n if (this.client) {\n client.startHeartbeat();\n this.set({ state: 'ready', progress: undefined });\n void client.fetchRfidInfo().then(() => this.set({ label: this.readRfid() }), () => {});\n }\n }\n }\n}\n","import type { PrinterModelMeta, PrintTaskName } from '@mmote/niimbluelib';\n\n/**\n * The slice of niimbluelib's client the app uses. Keeping it narrow lets the\n * mock printer and the tests stand in for real hardware.\n */\nexport interface PrintTaskLike {\n printInit(): Promise<void>;\n printPage(image: unknown, quantity?: number): Promise<void>;\n waitForFinished(): Promise<void>;\n printEnd(): Promise<boolean>;\n}\n\nexport interface ClientLike {\n connect(): Promise<{ deviceName?: string }>;\n disconnect(): Promise<void>;\n isConnected(): boolean;\n on(event: string, fn: (e: any) => void): unknown;\n off(event: string, fn: (e: any) => void): unknown;\n getPrinterInfo(): { batteryPercents?: number; modelId?: number; printheadWidth?: number; protocolVersion?: number };\n getModelMetadata(): PrinterModelMeta | undefined;\n getPrintTaskType(): PrintTaskName | undefined;\n getRfidInfo(): {\n labelRfidInfo?: { tagPresent?: boolean; barCode?: string; allPaper?: number; usedPaper?: number; consumablesType?: number };\n paperInfo?: { valid?: boolean; paperWidth?: number; paperHeight?: number; paperType?: number };\n };\n fetchRfidInfo(): Promise<unknown>;\n startHeartbeat(): void;\n stopHeartbeat(): void;\n isHeartbeatStarted?(): boolean;\n protocol: {\n newPrintTask(name: PrintTaskName, options: Record<string, unknown>): PrintTaskLike;\n /** True once the printer has finished every page. */\n printEnd(): Promise<boolean>;\n };\n /** Clients that encode pages themselves (cat printers); NIIMBOT pages go through niimbluelib's encoder. */\n encodePage?(canvas: HTMLCanvasElement): unknown;\n /**\n * Label printers that cannot sense the roll: a key to remember the roll\n * size the user sets, per printer. NIIMBOT printers read their rolls instead.\n */\n rollKey?(): string;\n /** Printers that cannot sense the paper: moves it by `dots`, back into the printer when negative. */\n feedPaper?(dots: number): Promise<void>;\n /** feedPaper can also move the paper back. */\n feedsBack?: boolean;\n /**\n * Bluetooth: the kind of printer to treat one with an unknown name as, set\n * before connecting: an id from PRINTER_TYPES, or a NIIMBOT profile id the\n * client ignores.\n */\n unrecognisedAs?(typeId: string | null): void;\n}\n\n/** `serial`: a printer on a USB cable that shows up as a serial port, as NIIMBOT printers do. `usb`: one that shows up as a standard USB printer. */\nexport type ConnectionKind = 'bluetooth' | 'serial' | 'usb' | 'demo';\n\nexport const supportsBluetooth = (): boolean => typeof navigator !== 'undefined' && 'bluetooth' in navigator;\nexport const supportsSerial = (): boolean => typeof navigator !== 'undefined' && 'serial' in navigator;\nexport const supportsUsb = (): boolean => typeof navigator !== 'undefined' && 'usb' in navigator;\n\nconst SUPPORTS: Record<Exclude<ConnectionKind, 'demo'>, () => boolean> = { bluetooth: supportsBluetooth, serial: supportsSerial, usb: supportsUsb };\n\nexport type Platform = 'ios' | 'android' | 'desktop';\n\nexport function detectPlatform(ua = typeof navigator === 'undefined' ? '' : navigator.userAgent, touchPoints = typeof navigator === 'undefined' ? 0 : navigator.maxTouchPoints): Platform {\n // iPadOS reports itself as a Mac; touch support gives it away.\n if (/iPhone|iPad|iPod/.test(ua) || (/Macintosh/.test(ua) && touchPoints > 1)) return 'ios';\n if (/Android/.test(ua)) return 'android';\n return 'desktop';\n}\n\nexport type OperatingSystem = 'ios' | 'android' | 'chromeos' | 'linux' | 'mac' | 'windows' | 'other';\nexport type BrowserName = 'chrome' | 'edge' | 'opera' | 'brave' | 'samsung' | 'bluefy' | 'firefox' | 'safari' | 'other';\n\nexport interface BrowserInfo {\n os: OperatingSystem;\n browser: BrowserName;\n /** Built on Chromium, so Web Bluetooth, Web Serial and WebUSB can be there. */\n chromium: boolean;\n /** Served over https or from localhost; browsers hide both APIs elsewhere. */\n secure: boolean;\n}\n\n/** What the browser tells about itself. Defaults to the running browser. */\nexport interface BrowserEnv {\n ua: string;\n touchPoints: number;\n /** Brave hides itself in the user agent; it has `navigator.brave` instead. */\n brave: boolean;\n secure: boolean;\n}\n\nfunction currentEnv(): BrowserEnv {\n if (typeof navigator === 'undefined') return { ua: '', touchPoints: 0, brave: false, secure: true };\n return {\n ua: navigator.userAgent,\n touchPoints: navigator.maxTouchPoints,\n brave: 'brave' in navigator,\n secure: typeof window === 'undefined' || window.isSecureContext !== false,\n };\n}\n\nexport function detectBrowser(env: Partial<BrowserEnv> = {}): BrowserInfo {\n const { ua, touchPoints, brave, secure } = { ...currentEnv(), ...env };\n const platform = detectPlatform(ua, touchPoints);\n const os: OperatingSystem =\n platform !== 'desktop' ? platform : /CrOS/.test(ua) ? 'chromeos' : /Windows/.test(ua) ? 'windows' : /Macintosh|Mac OS X/.test(ua) ? 'mac' : /Linux|X11|BSD/.test(ua) ? 'linux' : 'other';\n const browser: BrowserName = brave\n ? 'brave'\n : /Bluefy/i.test(ua)\n ? 'bluefy'\n : /Firefox|FxiOS/.test(ua)\n ? 'firefox'\n : /Edg(e|A|iOS)?\\//.test(ua)\n ? 'edge'\n : /OPR\\/|Opera/.test(ua)\n ? 'opera'\n : /SamsungBrowser/.test(ua)\n ? 'samsung'\n : /Chrome\\/|CriOS|Chromium/.test(ua)\n ? 'chrome'\n : /Safari\\//.test(ua)\n ? 'safari'\n : 'other';\n // Every iOS browser runs on WebKit, whatever its name.\n const chromium = os !== 'ios' && /Chrome\\/|Chromium/.test(ua);\n return { os, browser, chromium, secure };\n}\n\n/**\n * A Help topic that shows how to get a connection type working:\n * - `linux`: Chrome and other Chromium browsers on Linux need a flag for Bluetooth.\n * - `brave`: Brave keeps Bluetooth and USB behind flags.\n * - `ios`: iPhone and iPad browsers, and Bluefy.\n * - `https`: the page has to be served over https.\n * - `browsers`: which browsers work.\n * - `usb`: Windows and Linux hold on to USB printers until told to let the browser have them.\n */\nexport type SetupTopic = 'linux' | 'brave' | 'ios' | 'https' | 'browsers' | 'usb';\n\nexport interface SupportIssue {\n /** In a few words, what is missing or what to do. */\n reason: string;\n topic: SetupTopic;\n}\n\nconst BROWSER_NAMES: Partial<Record<BrowserName, string>> = { firefox: 'Firefox', safari: 'Safari', samsung: 'Samsung Internet', opera: 'Opera' };\n\n/**\n * Why a connection type cannot be used in this browser, and where to read how\n * to fix it, or null when it can be used.\n */\nexport function supportIssue(kind: ConnectionKind, env: Partial<BrowserEnv> = {}): SupportIssue | null {\n if (kind === 'demo') return null;\n if (SUPPORTS[kind]()) return null;\n const { os, browser, chromium, secure } = detectBrowser(env);\n const bluetooth = kind === 'bluetooth';\n if (os === 'ios') return { reason: bluetooth ? 'On iPhone and iPad, use the Bluefy browser' : 'Not available on iPhone and iPad', topic: 'ios' };\n if (!secure && chromium) return { reason: 'Needs an https page', topic: 'https' };\n if (browser === 'brave') return { reason: 'Turn it on in Brave flags', topic: 'brave' };\n if (os === 'android') {\n if (!bluetooth) return { reason: 'Needs Chrome or Edge on a computer', topic: 'browsers' };\n return { reason: browser === 'chrome' ? 'Update Chrome' : 'Open niimai in Chrome', topic: 'browsers' };\n }\n if (bluetooth && os === 'linux' && chromium) return { reason: 'Needs a Chrome flag on Linux', topic: 'linux' };\n const name = BROWSER_NAMES[browser];\n if (name) return { reason: `${name} has no ${bluetooth ? 'Bluetooth' : 'USB'} access; use Chrome or Edge`, topic: 'browsers' };\n if (chromium) return { reason: 'Turned off in this browser', topic: 'browsers' };\n return { reason: 'Needs Chrome or Edge', topic: 'browsers' };\n}\n\n/** Stand-ins for the platform-only signature below. */\nconst PLATFORM_ENVS: Record<Platform, Partial<BrowserEnv>> = {\n ios: { ua: 'iPhone', touchPoints: 5, brave: false },\n android: { ua: 'Android Firefox', touchPoints: 5, brave: false },\n desktop: { ua: '', touchPoints: 0, brave: false },\n};\n\n/** supportIssue's reason alone. */\nexport function unsupportedReason(kind: ConnectionKind, platform?: Platform): string | null {\n return supportIssue(kind, platform ? PLATFORM_ENVS[platform] : {})?.reason ?? null;\n}\n\n/**\n * Errors browsers raise when Bluetooth or USB is there but cannot be used:\n * the adapter is off or missing, the browser turned the API off, or the\n * system keeps a USB printer for itself. The Help topic to point to, or null.\n */\nexport function connectErrorTopic(err: unknown): SetupTopic | null {\n const message = err instanceof Error ? err.message : String(err);\n if (/globally disabled/i.test(message)) return 'brave';\n if (/claim interface|Access denied|Unable to open device/i.test(message)) return 'usb';\n if (/adapter not available|Bluetooth adapter|Bluetooth is (turned off|disabled|not available)/i.test(message)) return detectBrowser().os === 'linux' ? 'linux' : 'browsers';\n return null;\n}\n","/**\n * Label and receipt printers on a USB cable that show up as standard USB\n * printers (interface class 7), which Web Serial cannot see. They take the\n * same TSPL or ESC/POS bytes as over Bluetooth, through WebUSB. NIIMBOT\n * printers show up as serial ports and keep using Web Serial.\n */\nimport { cableClient, findPrinterType, identifyUsbPrinter, type CatalogEntry, type PrinterFamily, type PrinterType } from './catalog';\nimport { supportsUsb } from './client';\nimport type { ThermalTransport } from './thermal';\nimport { RoutedClient } from './routed';\n\nexport const USB_PRINTER_CLASS = 7;\n\n/** USB moves data far faster than Bluetooth and paces itself, so writes are large and back to back. */\nconst USB_PACE = { chunkMs: 0, pauseMs: 100 };\nconst USB_CHUNK = 4096;\n/** Label printers may not send \"OK\" over USB; a status query after this long tells a fault from a quiet printer. */\nconst USB_ACK_TIMEOUT_MS = 5_000;\n\n/**\n * The fields of an IEEE 1284 device ID, keys in capitals:\n * \"MFG:ACME;COMMAND SET:TSC/ESC/POS;MODEL:CC390;\" → { MFG, 'COMMAND SET', MODEL }.\n */\nexport function parseDeviceId(text: string): Record<string, string> {\n const fields: Record<string, string> = {};\n for (const part of text.split(';')) {\n const at = part.indexOf(':');\n if (at > 0) fields[part.slice(0, at).trim().toUpperCase()] = part.slice(at + 1).trim();\n }\n return fields;\n}\n\n/** The printer languages a device ID names, TSPL first: label printers often take ESC/POS as well. */\nexport function familiesFromDeviceId(text: string): PrinterFamily[] {\n const id = parseDeviceId(text);\n const commands = id.CMD ?? id['COMMAND SET'] ?? '';\n const families: PrinterFamily[] = [];\n if (/\\b(TSPL2?|TSC)\\b/i.test(commands)) families.push('tspl');\n if (/ESC\\/?POS|\\bEPSON\\b/i.test(commands)) families.push('escpos');\n return families;\n}\n\nconst isPrinter = (device: USBDevice) =>\n device.configuration?.interfaces.some((i) => i.alternates.some((a) => a.interfaceClass === USB_PRINTER_CLASS)) ??\n // Devices not yet opened show no configuration; the chooser only lists printers anyway.\n true;\n\n/** USB printers this site may already use that are plugged in. */\nexport async function rememberedUsbPrinters(): Promise<USBDevice[]> {\n if (!supportsUsb()) return [];\n return (await navigator.usb.getDevices().catch(() => [])).filter(isPrinter);\n}\n\n/** Opens a USB printer and claims its printer interface. Also reads the printer's device ID. */\nexport async function usbTransport(device: USBDevice): Promise<{ transport: ThermalTransport; deviceId: string }> {\n await device.open();\n if (!device.configuration) await device.selectConfiguration(1);\n const iface = device.configuration!.interfaces.find((i) => i.alternates.some((a) => a.interfaceClass === USB_PRINTER_CLASS));\n if (!iface) throw new Error(`${device.productName ?? 'This device'} is not a USB printer`);\n const alt = iface.alternates.find((a) => a.interfaceClass === USB_PRINTER_CLASS)!;\n await device.claimInterface(iface.interfaceNumber);\n if (alt.alternateSetting !== 0) await device.selectAlternateInterface(iface.interfaceNumber, alt.alternateSetting);\n const out = alt.endpoints.find((e) => e.direction === 'out' && e.type === 'bulk');\n const back = alt.endpoints.find((e) => e.direction === 'in' && e.type === 'bulk');\n if (!out) throw new Error(`${device.productName ?? 'This printer'} has no way to send it data`);\n\n // GET_DEVICE_ID: interface in the high byte of the index, alternate setting in the low byte.\n const deviceId = await device\n .controlTransferIn({ requestType: 'class', recipient: 'interface', request: 0, value: 0, index: (iface.interfaceNumber << 8) | alt.alternateSetting }, 1024)\n .then((r) => (r.data ? new TextDecoder().decode(new Uint8Array(r.data.buffer, r.data.byteOffset + 2, Math.max(0, r.data.byteLength - 2))) : ''))\n .catch(() => '');\n\n const notify: ((d: Uint8Array) => void)[] = [];\n const gone: (() => void)[] = [];\n let open = true;\n // Replies (TSPL's \"OK\", status bytes) arrive on the bulk in endpoint; read it until the device closes.\n if (back) {\n void (async () => {\n while (open) {\n const r = await device.transferIn(back.endpointNumber, back.packetSize).catch(() => null);\n if (!r) break;\n if (r.data?.byteLength) notify.forEach((fn) => fn(new Uint8Array(r.data!.buffer.slice(r.data!.byteOffset, r.data!.byteOffset + r.data!.byteLength))));\n }\n })();\n }\n const onUnplug = (e: USBConnectionEvent) => {\n if (e.device !== device) return;\n open = false;\n navigator.usb.removeEventListener('disconnect', onUnplug);\n gone.forEach((fn) => fn());\n };\n navigator.usb.addEventListener('disconnect', onUnplug);\n\n return {\n deviceId,\n transport: {\n name: device.productName || undefined,\n chunkBytes: USB_CHUNK,\n write: async (chunk) => {\n const r = await device.transferOut(out.endpointNumber, chunk.slice());\n if (r.status !== 'ok') throw new Error('The printer did not take the data');\n },\n onNotify: (fn) => notify.push(fn),\n onDisconnect: (fn) => gone.push(fn),\n disconnect: async () => {\n open = false;\n navigator.usb.removeEventListener('disconnect', onUnplug);\n await device.releaseInterface(iface.interfaceNumber).catch(() => {});\n await device.close().catch(() => {});\n },\n },\n };\n}\n\nconst PRINTER_TYPE_NAMES: Record<PrinterFamily, string> = { escpos: 'receipt printer', tspl: 'label printer', cat: 'cat printer' };\n\n/**\n * The USB printer behind the USB button for label and receipt printers. A\n * printer this site already uses connects straight away when it is the only\n * one plugged in; otherwise the browser lists USB printers. The printer's\n * language comes from the catalog, then from the type picked under Advanced\n * when the printer speaks it (or does not say), then from the device ID the\n * printer reports.\n */\nexport class UsbPrinter extends RoutedClient {\n private fallback: PrinterType | null = null;\n\n unrecognisedAs(typeId: string | null) {\n this.fallback = findPrinterType(typeId);\n }\n\n async connect() {\n const remembered = await rememberedUsbPrinters();\n const device = remembered.length === 1 ? remembered[0] : await navigator.usb.requestDevice({ filters: [{ classCode: USB_PRINTER_CLASS }] });\n const { transport, deviceId } = await usbTransport(device);\n const name = device.productName || 'This printer';\n const families = familiesFromDeviceId(deviceId);\n const picked = this.fallback && (!families.length || families.includes(this.fallback.family)) ? this.fallback : null;\n const entry: CatalogEntry | null =\n identifyUsbPrinter(device) ?? picked ?? (families.length ? { family: families[0], model: { prefix: '', confidence: 'medium', generic: true } } : null);\n if (!entry) {\n await transport.disconnect();\n throw new Error(`${name} did not say which printer language it speaks. Pick its type under Advanced and connect again.`);\n }\n this.inner = cableClient(entry, transport, USB_PACE, USB_ACK_TIMEOUT_MS);\n if (!this.inner) {\n await transport.disconnect();\n throw new Error(`A ${PRINTER_TYPE_NAMES[entry.family]} cannot print over USB`);\n }\n return this.inner.connect();\n }\n}\n","/**\n * libniimai: connect to NIIMBOT, TSPL, ESC/POS and cat printers from the\n * browser over Web Bluetooth, Web Serial or WebUSB, and print a canvas on them.\n */\n/// <reference types=\"web-bluetooth\" preserve=\"true\" />\n/// <reference types=\"w3c-web-serial\" preserve=\"true\" />\n/// <reference types=\"w3c-web-usb\" preserve=\"true\" />\nimport { BluetoothPrinter } from './bluetooth';\nimport type { ClientLike, ConnectionKind } from './client';\nimport { MockClient } from './mock';\nimport { PrinterService, type ClientFactory } from './service';\nimport { UsbPrinter } from './usb';\n\n// niimbluelib loads only when it is needed, so pages that never meet a NIIMBOT stay light.\nconst niimbot = () => import('@mmote/niimbluelib');\n\n/**\n * A client for one kind of connection. Bluetooth finds the printer family\n * from the device the user picks; serial is NIIMBOT only; usb is for label\n * and receipt printers that show up as standard USB printers; demo is a\n * printer in memory.\n */\nexport async function createClient(kind: ConnectionKind): Promise<ClientLike> {\n if (kind === 'demo') return new MockClient();\n if (kind === 'bluetooth') return new BluetoothPrinter(niimbot);\n if (kind === 'usb') return new UsbPrinter();\n const lib = await niimbot();\n return new lib.NiimbotSerialClient() as unknown as ClientLike;\n}\n\nexport interface CreatePrinterOptions {\n /**\n * Makes the client for each connection. Defaults to createClient; wrap it\n * to change one kind of connection and keep the others.\n */\n createClient?: ClientFactory;\n}\n\n/** A ready printer service: connect, watch its status, print canvases. */\nexport function createPrinter(options: CreatePrinterOptions = {}): PrinterService {\n return new PrinterService(options.createClient ?? createClient, async (canvas, direction) =>\n (await niimbot()).ImageEncoder.encodeCanvas(canvas, 0, direction),\n );\n}\n\nexport { PrinterService, DEMO_NAME, friendlyError, fitToPrinthead, rotateCanvas, shiftCanvas } from './service';\nexport type { ClientFactory, PageSource, PrinterState, PrinterStatus, PrintOffset, PrintOptions, Rotation } from './service';\n\nexport { connectErrorTopic, detectBrowser, detectPlatform, supportIssue, supportsBluetooth, supportsSerial, supportsUsb, unsupportedReason } from './client';\nexport type { BrowserEnv, BrowserInfo, BrowserName, ClientLike, ConnectionKind, OperatingSystem, Platform, PrintTaskLike, SetupTopic, SupportIssue } from './client';\n\nexport { correctTask, findManualProfile, inkOutsideHead, MANUAL_PROFILES, printheadCrop, printheadMm, resolveProfile } from './profiles';\nexport type { LabelSize, PrintDirection, PrinterProfile } from './profiles';\n\nexport { effectivePaper, labelTypeOf, PAPER_TYPES, paperFromLabelType, paperName } from './paper';\nexport type { PaperType } from './paper';\n\nexport { findPrinterType, identifyPrinter, identifyUsbPrinter, PRINTER_CATALOG, PRINTER_TYPES } from './catalog';\nexport type { CatalogEntry, PrinterFamily, PrinterType } from './catalog';\n\nexport { BluetoothPrinter } from './bluetooth';\nexport { familiesFromDeviceId, parseDeviceId, rememberedUsbPrinters, UsbPrinter, usbTransport, USB_PRINTER_CLASS } from './usb';\nexport { describeBleError } from './ble';\nexport { DEMO_META, MockClient } from './mock';\n\nexport { modelFor, THERMAL_DEFAULT_SIZE, THERMAL_HEAD_DOTS, THERMAL_HEAD_MM, THERMAL_SIZES, ThermalPrinterClient, webTransport } from './thermal';\nexport type { Confidence, Pace, ThermalModel, ThermalTransport } from './thermal';\n\nexport { CAT_MODELS, CAT_UNSUPPORTED, CatPrinterClient, catFeed, catModelFor, printJob as catJob } from './cat';\nexport type { CatModel } from './cat';\nexport { ESCPOS_MODELS, EscPosPrinterClient, escposFeed, escposJob, escposModelFor } from './escpos';\nexport { TSPL_MODELS, TSPL_SIZES, TsplPrinterClient, tsplJob, tsplModelFor } from './tspl';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"libniimai.js","names":["sleep","encodeRows","encodeRows","sleep","PRINTER_TYPE_NAMES","sleep"],"sources":["../src/ble.ts","../src/thermal.ts","../src/cat.ts","../src/escpos.ts","../src/tspl.ts","../src/catalog.ts","../src/routed.ts","../src/bluetooth.ts","../src/mock.ts","../src/paper.ts","../src/profiles.ts","../src/service.ts","../src/client.ts","../src/usb.ts","../src/index.ts"],"sourcesContent":["import type { ConnectionInfo, NiimbotAbstractClient } from '@mmote/niimbluelib';\n\ntype Lib = typeof import('@mmote/niimbluelib');\n\n/** The standard NIIMBOT service, followed by serial-over-BLE services common on budget printers. */\nexport const CANDIDATE_SERVICES = [\n 'e7810a71-73ae-499d-8c15-faa9aef0c3f2', // NIIMBOT\n '0000e0ff-3c17-d293-8e48-14fe2e4da212', // NIIMBOT B21 C2B (model id 771), channel ffe1\n '0000ae30-0000-1000-8000-00805f9b34fb', // AE30 printer UART\n '0000ff00-0000-1000-8000-00805f9b34fb',\n '0000ffe0-0000-1000-8000-00805f9b34fb', // HM-10 style\n '0000fff0-0000-1000-8000-00805f9b34fb',\n '000018f0-0000-1000-8000-00805f9b34fb', // generic printer\n '49535343-fe7d-4ae5-8fa9-9fafd205e455', // Microchip transparent UART\n '6e400001-b5a3-f393-e0a9-e50e24dcca9e', // Nordic UART\n];\n\n/** Name prefixes of NIIMBOT printers; the chooser lists only these and known cat printers. */\nexport const NAME_PREFIXES = ['A', 'B', 'C', 'D', 'E', 'F', 'H', 'J', 'K', 'M', 'N', 'P', 'S', 'T', 'Z'];\n\n/** Opens the browser's printer chooser. `all` lists every device, for printers whose names niimai does not know. */\nexport function requestPrinter(extraPrefixes: string[] = [], all = false): Promise<BluetoothDevice> {\n if (all) return navigator.bluetooth.requestDevice({ acceptAllDevices: true, optionalServices: CANDIDATE_SERVICES });\n return navigator.bluetooth.requestDevice({\n filters: [...[...NAME_PREFIXES, ...extraPrefixes].map((namePrefix) => ({ namePrefix })), { services: [CANDIDATE_SERVICES[0]] }],\n optionalServices: CANDIDATE_SERVICES,\n });\n}\n\ninterface CharLike {\n uuid: string;\n properties: { notify: boolean; indicate?: boolean; write: boolean; writeWithoutResponse: boolean };\n}\n\nexport interface ServiceLike<C extends CharLike = CharLike> {\n uuid: string;\n getCharacteristics(): Promise<C[]>;\n}\n\nexport interface Channel<C extends CharLike = CharLike> {\n write: C;\n notify: C;\n}\n\n/**\n * Picks the characteristics to talk through. A single characteristic that both\n * notifies and accepts writes wins (the NIIMBOT layout); otherwise a write and\n * a notify characteristic from the same service are paired.\n */\nexport async function pickChannel<C extends CharLike>(services: ServiceLike<C>[]): Promise<Channel<C> | null> {\n const all: { service: string; chars: C[] }[] = [];\n for (const service of services) all.push({ service: service.uuid, chars: await service.getCharacteristics() });\n\n const canWrite = (c: C) => c.properties.writeWithoutResponse || c.properties.write;\n const canNotify = (c: C) => c.properties.notify || !!c.properties.indicate;\n\n for (const { chars } of all) {\n const both = chars.find((c) => c.properties.writeWithoutResponse && c.properties.notify);\n if (both) return { write: both, notify: both };\n }\n for (const { chars } of all) {\n const write = chars.find(canWrite);\n const notify = chars.find(canNotify);\n if (write && notify) return { write, notify };\n }\n return null;\n}\n\n/** Turns Chrome's terse GATT errors into something a person can act on. */\nexport function describeBleError(err: unknown, found: string[] = []): Error {\n const msg = err instanceof Error ? err.message : String(err);\n if (/no services found/i.test(msg) || (found.length === 0 && /characteristic/i.test(msg))) {\n return new Error('This printer uses an unknown Bluetooth service. See Help → Printer not recognised.');\n }\n if (/characteristic/i.test(msg)) return new Error(`No usable channel. Services: ${found.join(', ')}`);\n return err instanceof Error ? err : new Error(msg);\n}\n\n/**\n * A Web Bluetooth client that accepts more service layouts than niimbluelib's\n * own, which only knows the standard NIIMBOT service. The protocol handling\n * (packets, negotiation, heartbeat) is inherited unchanged.\n */\nexport function createBleClient(lib: Lib, chosen?: BluetoothDevice): NiimbotAbstractClient {\n class FlexibleBleClient extends lib.NiimbotAbstractClient {\n private server?: BluetoothRemoteGATTServer;\n private channel?: Channel<BluetoothRemoteGATTCharacteristic>;\n\n async connect(): Promise<ConnectionInfo> {\n await this.disconnect();\n const device = chosen ?? (await requestPrinter());\n if (!device.gatt) throw new Error('Device has no GATT server');\n\n const onDisconnect = () => {\n this.server = undefined;\n this.channel = undefined;\n this.emit('disconnect', new lib.DisconnectEvent());\n device.removeEventListener('gattserverdisconnected', onDisconnect);\n };\n device.addEventListener('gattserverdisconnected', onDisconnect);\n\n const server = await device.gatt.connect();\n let found: string[] = [];\n try {\n const services = await server.getPrimaryServices();\n found = services.map((s) => s.uuid);\n console.info('[niimai] services', found);\n const channel = await pickChannel(services);\n if (!channel) throw new Error('Suitable characteristic not found');\n console.info('[niimai] channel', channel.write.uuid, channel.notify.uuid);\n channel.notify.addEventListener('characteristicvaluechanged', (e) => {\n const value = (e.target as BluetoothRemoteGATTCharacteristic).value;\n if (value) this.processRawPacket(value);\n });\n await channel.notify.startNotifications();\n this.server = server;\n this.channel = channel;\n } catch (err) {\n server.disconnect();\n throw describeBleError(err, found);\n }\n\n await this.negotiateAndGetPrinterInfo(() => this.disconnect());\n const result: ConnectionInfo = {\n deviceName: device.name,\n result: this.printerInfo.connectResult ?? lib.ConnectResult.FirmwareErrors,\n };\n this.emit('connect', new lib.ConnectEvent(result));\n return result;\n }\n\n isConnected(): boolean {\n return !!this.server && !!this.channel;\n }\n\n async disconnect(): Promise<void> {\n this.stopHeartbeat();\n this.server?.disconnect();\n this.server = undefined;\n this.channel = undefined;\n }\n\n async sendRaw(data: Uint8Array, force?: boolean): Promise<void> {\n const send = async () => {\n const c = this.channel?.write;\n if (!c) throw new Error('Channel is closed');\n await lib.Utils.sleep(this.packetIntervalMs);\n const buf = data.slice().buffer;\n if (c.properties.writeWithoutResponse) await c.writeValueWithoutResponse(buf);\n else await c.writeValueWithResponse(buf);\n this.emit('rawpacketsent', new lib.RawPacketSentEvent(data));\n };\n if (force) await send();\n else await this.mutex.runExclusive(send);\n }\n\n getType() {\n return 'bluetooth' as const;\n }\n }\n return new FlexibleBleClient();\n}\n","/**\n * Shared ground for the extra Bluetooth thermal printers: cat printers and\n * ESC/POS mini printers (58 mm continuous paper, 384-dot head) and TSPL\n * label printers (labels with gaps, up to 80 mm). niimai's focus is NIIMBOT;\n * these clients are kept apart so NIIMBOT printing never goes through them.\n */\nimport type { PrinterModelMeta, PrintTaskName } from '@mmote/niimbluelib';\nimport type { LabelSize } from './profiles';\nimport type { ClientLike, PrintTaskLike } from './client';\n\n/** Dots across the head: 48 mm at 203 dpi. */\nexport const THERMAL_HEAD_DOTS = 384;\n/** The print width in whole millimetres, the widest a design on continuous paper can be. */\nexport const THERMAL_HEAD_MM = 48;\n\nexport type Confidence = 'tested' | 'high' | 'medium';\n\nexport interface ThermalModel {\n /** Bluetooth name prefix. */\n prefix: string;\n confidence: Confidence;\n note?: string;\n /** The prefix names a brand or a generic Bluetooth module, not a model; the printer's own name is shown. */\n generic?: boolean;\n /** Maker and model, where the Bluetooth name does not say. */\n brand?: string;\n /** Dots across the head, where it differs from the family's usual one: 576 on 80 mm receipt printers. */\n headDots?: number;\n /** USB vendor and product IDs, for printers that also connect by cable as a standard USB printer. */\n usbIds?: [vendorId: number, productId: number][];\n}\n\n/** The name a printer shows: its model, or its own Bluetooth name when the model entry is only a brand or a stock name. */\nexport const shownName = (model: ThermalModel | null, transport: { name?: string }, fallback: string): string =>\n (model && !model.generic ? model.prefix : transport.name) || model?.prefix || fallback;\n\n/** The known model a Bluetooth name belongs to, longest prefix first. */\nexport function modelFor<M extends ThermalModel>(models: M[], name: string | undefined): M | null {\n if (!name) return null;\n return [...models].sort((a, b) => b.prefix.length - a.prefix.length).find((m) => name.startsWith(m.prefix)) ?? null;\n}\n\n/** Sizes that suit 58 mm continuous paper: the full 48 mm print width at common lengths. */\nexport const THERMAL_SIZES: LabelSize[] = [15, 20, 30, 48, 80].map((heightMm) => ({ widthMm: 48, heightMm }));\nexport const THERMAL_DEFAULT_SIZE: LabelSize = { widthMm: 48, heightMm: 30 };\n\n/**\n * Packs a 1-bit page (RGBA, dark = black) into rows as wide as the head, centred across\n * the head. Cat printers want the leftmost dot in the lowest bit, ESC/POS in\n * the highest.\n */\nexport function packRows(data: ArrayLike<number>, width: number, height: number, order: 'lsb' | 'msb', head = THERMAL_HEAD_DOTS): Uint8Array[] {\n const offset = Math.max(0, Math.floor((head - width) / 2));\n const rows: Uint8Array[] = [];\n for (let y = 0; y < height; y++) {\n const row = new Uint8Array(head / 8);\n for (let x = 0; x < width && x + offset < head; x++) {\n if (data[(y * width + x) * 4] < 128) {\n const dot = x + offset;\n row[dot >> 3] |= order === 'lsb' ? 1 << (dot & 7) : 0x80 >> (dot & 7);\n }\n }\n rows.push(row);\n }\n return rows;\n}\n\n/** The Bluetooth or USB link, kept narrow so tests can stand in for it. */\nexport interface ThermalTransport {\n name?: string;\n /** Bytes per write, for links that take more than Bluetooth's cautious start. */\n chunkBytes?: number;\n write(chunk: Uint8Array): Promise<void>;\n onNotify(fn: (data: Uint8Array) => void): void;\n onDisconnect(fn: () => void): void;\n disconnect(): Promise<void>;\n}\n\nexport interface Pace {\n /** Pause after each write. */\n chunkMs: number;\n /** How often to look again while the printer asks to wait. */\n pauseMs: number;\n /** Pause after a reset command that starts a job: some printers drop what arrives while they reset. */\n resetMs?: number;\n}\n\nexport const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\n/**\n * A thermal printer behind the same interface as a NIIMBOT client, so the\n * printer service drives both. It reports itself through model metadata with\n * kind \"thermal\"; it has no roll tags, battery or heartbeat to offer.\n * Subclasses supply the row encoding and the bytes for a job.\n */\nexport abstract class ThermalPrinterClient implements ClientLike {\n private listeners = new Map<string, Set<(e: unknown) => void>>();\n private connected = false;\n /** Set while the printer asks to wait (cat printers' flow control). */\n protected paused = false;\n /** Bytes per write. Starts large and halves if the link refuses it. */\n private chunk: number;\n\n protected abstract readonly densityLevels: number;\n /** The model name shown for this printer. */\n protected abstract readonly modelName: string;\n /** Dots across the head. */\n protected readonly headDots: number = THERMAL_HEAD_DOTS;\n /** \"left\": the label runs sideways through the head, as on NIIMBOT D-series printers. */\n protected readonly printDirection: 'top' | 'left' = 'top';\n /** Continuous-paper printers report \"thermal\"; label printers report nothing and get the roll UI. */\n protected readonly kind: 'thermal' | undefined = 'thermal';\n /** Label sizes common for this printer, offered in place of NIIMBOT roll sizes. */\n protected readonly labelSizes: LabelSize[] | undefined = undefined;\n protected abstract encodeRows(data: ArrayLike<number>, width: number, height: number): Uint8Array[];\n /** `gapDots`: labels with gaps on a printer that cannot sense them; each label is followed by exactly this much feed. */\n protected abstract job(rows: Uint8Array[], density: number, copies: number, labelType: number, gapDots?: number): Uint8Array;\n /** Called after each page is sent; printers that confirm a job wait here. */\n protected async afterPage(): Promise<void> {}\n\n constructor(\n protected transport: ThermalTransport,\n protected pace: Pace,\n firstChunk: number,\n ) {\n this.chunk = transport.chunkBytes ?? firstChunk;\n transport.onDisconnect(() => {\n this.connected = false;\n this.emit('disconnect', {});\n });\n }\n\n async connect() {\n this.connected = true;\n return { deviceName: this.transport.name };\n }\n\n async disconnect() {\n this.connected = false;\n await this.transport.disconnect();\n }\n\n isConnected() {\n return this.connected;\n }\n\n on(event: string, fn: (e: unknown) => void) {\n if (!this.listeners.has(event)) this.listeners.set(event, new Set());\n this.listeners.get(event)!.add(fn);\n }\n\n off(event: string, fn: (e: unknown) => void) {\n this.listeners.get(event)?.delete(fn);\n }\n\n private emit(event: string, e: unknown) {\n this.listeners.get(event)?.forEach((fn) => fn(e));\n }\n\n getPrinterInfo() {\n return {};\n }\n\n getModelMetadata(): PrinterModelMeta {\n return {\n model: this.modelName,\n ...(this.kind ? { kind: this.kind } : {}),\n ...(this.labelSizes ? { labelSizes: this.labelSizes } : {}),\n dpi: 203,\n printheadPixels: this.headDots,\n printDirection: this.printDirection,\n densityMin: 1,\n densityMax: this.densityLevels,\n densityDefault: Math.ceil(this.densityLevels / 2),\n } as unknown as PrinterModelMeta;\n }\n\n getPrintTaskType(): PrintTaskName {\n // The service needs a task name; these clients print the same way whatever it is.\n return 'B1' as PrintTaskName;\n }\n\n getRfidInfo() {\n return {};\n }\n\n async fetchRfidInfo() {}\n startHeartbeat() {}\n stopHeartbeat() {}\n\n /** Pages arrive as 1-bit canvases; the service hands them here instead of to niimbluelib's encoder. */\n encodePage(canvas: HTMLCanvasElement): Uint8Array[] {\n const ctx = canvas.getContext('2d');\n if (!ctx) throw new Error('Canvas is unavailable');\n return this.encodeRows(ctx.getImageData(0, 0, canvas.width, canvas.height).data, canvas.width, canvas.height);\n }\n\n /** Sends bytes in chunks, waiting while the printer asks for a pause. `onSent` hears the share sent, 0–1. */\n async send(bytes: Uint8Array, onSent?: (share: number) => void): Promise<void> {\n for (let i = 0; i < bytes.length; ) {\n while (this.paused) await sleep(this.pace.pauseMs);\n const part = bytes.subarray(i, i + this.chunk);\n try {\n await this.transport.write(part);\n } catch (err) {\n if (this.chunk <= 20) throw err;\n this.chunk = Math.max(20, this.chunk >> 1);\n continue;\n }\n i += part.length;\n onSent?.(i / bytes.length);\n await sleep(this.pace.chunkMs);\n }\n }\n\n protocol = {\n printEnd: async () => true,\n newPrintTask: (_name: PrintTaskName, options: Record<string, unknown>): PrintTaskLike => {\n const density = Number(options.density ?? 1);\n const labelType = Number(options.labelType ?? 1);\n const gapDots = options.gapDots === undefined ? undefined : Math.max(0, Math.round(Number(options.gapDots)));\n // A batch sends several pages to one task; progress counts across them.\n let printed = 0;\n return {\n printInit: async () => {},\n printPage: async (image, quantity = 1) => {\n const total = () => Math.max(printed, Number(options.totalPages ?? printed));\n // Sending is the slow part over Bluetooth, so report it as it goes, in whole percents.\n let shown = -1;\n await this.send(this.job(image as Uint8Array[], density, Math.max(1, quantity), labelType, gapDots), (share) => {\n const sending = Math.floor(share * 100);\n if (sending === shown) return;\n shown = sending;\n this.emit('printprogress', { page: printed, pagesTotal: total(), pagePrintProgress: 0, pageFeedProgress: 0, sending });\n });\n await this.afterPage();\n printed += Math.max(1, quantity);\n this.emit('printprogress', { page: printed, pagesTotal: total(), pagePrintProgress: 100, pageFeedProgress: 100 });\n },\n waitForFinished: async () => {},\n printEnd: async () => true,\n };\n },\n };\n}\n\n/** A Web Bluetooth link through one service: a write characteristic and, if present, a notify one. */\nexport async function webTransport(\n device: BluetoothDevice,\n server: BluetoothRemoteGATTServer,\n ids: { service: string; write: string; notify?: string },\n): Promise<ThermalTransport> {\n const service = await server.getPrimaryService(ids.service);\n const tx = await service.getCharacteristic(ids.write);\n const notify: ((d: Uint8Array) => void)[] = [];\n if (ids.notify) {\n const rx = await service.getCharacteristic(ids.notify);\n rx.addEventListener('characteristicvaluechanged', (e) => {\n const v = (e.target as BluetoothRemoteGATTCharacteristic).value;\n if (v) notify.forEach((fn) => fn(new Uint8Array(v.buffer.slice(v.byteOffset, v.byteOffset + v.byteLength))));\n });\n await rx.startNotifications();\n }\n return {\n name: device.name,\n write: async (chunk) => {\n const buf = chunk.slice().buffer;\n if (tx.properties.writeWithoutResponse) await tx.writeValueWithoutResponse(buf);\n else await tx.writeValueWithResponse(buf);\n },\n onNotify: (fn) => notify.push(fn),\n onDisconnect: (fn) => device.addEventListener('gattserverdisconnected', fn),\n disconnect: async () => server.disconnect(),\n };\n}\n","/**\n * \"Cat printers\": the small Bluetooth thermal printers sold as GB01, MX06 and\n * the like. See thermal.ts for what they share with other extras.\n *\n * Protocol, from rbaron/catprinter and NaitLee/Cat-Printer: packets are\n * 51 78 <cmd> 00 <len> 00 <payload> <crc8(payload)> FF over service AE30,\n * written to AE01. The printer sends flow control on AE02.\n */\nimport { modelFor, packRows, shownName, THERMAL_HEAD_DOTS, ThermalPrinterClient, type Confidence, type Pace, type ThermalModel, type ThermalTransport } from './thermal';\n\nexport const CAT_SERVICE = '0000ae30-0000-1000-8000-00805f9b34fb';\nexport const CAT_TX = '0000ae01-0000-1000-8000-00805f9b34fb';\nexport const CAT_RX = '0000ae02-0000-1000-8000-00805f9b34fb';\n\nconst ROW_BYTES = THERMAL_HEAD_DOTS / 8;\n\nexport type { Confidence };\n\nexport interface CatModel extends ThermalModel {\n /** Takes the newer \"start printing\" command. */\n newKind?: boolean;\n /** Mishandles the feed command, so the paper is fed with blank rows. */\n feedWithRows?: boolean;\n}\n\n/**\n * Models known to speak this protocol. \"high\": supported by both reference\n * projects and widely used. \"medium\": listed by Cat-Printer, fewer reports.\n * \"tested\": printed with niimai on real hardware.\n */\nexport const CAT_MODELS: CatModel[] = [\n { prefix: 'GB01', confidence: 'high' },\n { prefix: 'GB02', confidence: 'high' },\n { prefix: 'GB03', confidence: 'high', newKind: true },\n { prefix: 'MX05', confidence: 'high', feedWithRows: true },\n { prefix: 'MX06', confidence: 'high', feedWithRows: true },\n { prefix: 'MX10', confidence: 'high', feedWithRows: true },\n { prefix: 'MX08', confidence: 'medium', feedWithRows: true },\n { prefix: 'MX09', confidence: 'medium', feedWithRows: true },\n { prefix: 'MX11', confidence: 'medium' },\n { prefix: 'GT01', confidence: 'medium' },\n { prefix: 'YT01', confidence: 'medium' },\n { prefix: 'PD01', confidence: 'medium' },\n { prefix: 'MXTP', confidence: 'medium' },\n { prefix: 'SC03h', confidence: 'medium' },\n];\n\n/** Look-alikes that use other protocols. Listed in Help and docs/thermal-printers.md. */\nexport const CAT_UNSUPPORTED: { name: string; note: string }[] = [\n { name: 'MXW01', note: 'Newer protocol' },\n { name: 'PeriPage', note: 'Different protocol' },\n { name: 'Phomemo M02 / T02', note: 'Different protocol' },\n];\n\nexport const catModelFor = (name: string | undefined): CatModel | null => modelFor(CAT_MODELS, name);\n\n// CRC-8, polynomial 0x07, as the printers expect.\nconst CRC_TABLE = Array.from({ length: 256 }, (_, i) => {\n let c = i;\n for (let b = 0; b < 8; b++) c = c & 0x80 ? ((c << 1) ^ 0x07) & 0xff : (c << 1) & 0xff;\n return c;\n});\nexport const crc8 = (data: ArrayLike<number>): number => {\n let c = 0;\n for (let i = 0; i < data.length; i++) c = CRC_TABLE[(c ^ data[i]) & 0xff];\n return c;\n};\n\nexport function packet(cmd: number, payload: ArrayLike<number>): number[] {\n if (payload.length > 0xff) throw new Error('Payload too long');\n return [0x51, 0x78, cmd, 0x00, payload.length, 0x00, ...Array.from(payload), crc8(payload), 0xff];\n}\n\nconst u16 = (v: number) => [v & 0xff, (v >> 8) & 0xff];\n\nconst CMD = {\n retract: 0xa0,\n feed: 0xa1,\n row: 0xa2,\n state: 0xa3,\n quality: 0xa4,\n lattice: 0xa6,\n update: 0xa9,\n energy: 0xaf,\n speed: 0xbd,\n applyEnergy: 0xbe,\n} as const;\n\nconst LATTICE_START = [0xaa, 0x55, 0x17, 0x38, 0x44, 0x5f, 0x5f, 0x5f, 0x44, 0x38, 0x2c];\nconst LATTICE_END = [0xaa, 0x55, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17];\n\n/** Flow control the printer sends on AE02 when its buffer fills and drains. */\nexport const FLOW_PAUSE = [0x51, 0x78, 0xae, 0x01, 0x01, 0x00, 0x10, 0x70, 0xff];\nexport const FLOW_RESUME = [0x51, 0x78, 0xae, 0x01, 0x01, 0x00, 0x00, 0x00, 0xff];\n\n/** Heat energy for each darkness step; Cat-Printer prints images at 0x4000. */\nconst ENERGY = [0x2000, 0x3000, 0x4000, 0x6000, 0x9000];\n/** Rows fed after the last copy, so the print clears the tear bar. */\nconst FEED_AFTER = 128;\n/** Blank rows between copies. */\nconst GAP_BETWEEN = 24;\n\n/** Rows for a cat printer: leftmost dot in the lowest bit. */\nexport const encodeRows = (data: ArrayLike<number>, width: number, height: number): Uint8Array[] => packRows(data, width, height, 'lsb');\n\n/**\n * Everything sent for one job: setup, the rows for each copy, then feed and\n * finish. With `gapDots`, for labels with gaps, every label is followed by\n * exactly that feed, so the next print starts on the next label.\n */\nexport function printJob(rows: Uint8Array[], { density, copies, model, gapDots }: { density: number; copies: number; model: CatModel | null; gapDots?: number }): Uint8Array {\n const out: number[] = [];\n const push = (cmd: number, payload: ArrayLike<number>) => out.push(...packet(cmd, payload));\n push(CMD.state, [0]);\n if (model?.newKind) out.push(0x12);\n push(CMD.state, [0]);\n push(CMD.quality, [50]);\n push(CMD.speed, [32]);\n push(CMD.energy, u16(ENERGY[Math.min(ENERGY.length, Math.max(1, Math.round(density))) - 1]));\n push(CMD.applyEnergy, [1]);\n push(CMD.update, [0]);\n push(CMD.lattice, LATTICE_START);\n const blank = new Uint8Array(ROW_BYTES);\n for (let c = 0; c < copies; c++) {\n if (c > 0) for (let i = 0; i < (gapDots ?? GAP_BETWEEN); i++) push(CMD.row, blank);\n for (const row of rows) push(CMD.row, row);\n }\n push(CMD.lattice, LATTICE_END);\n push(CMD.speed, [8]);\n if (gapDots !== undefined) out.push(...catFeed(gapDots, model));\n else if (model?.feedWithRows) for (let i = 0; i < FEED_AFTER; i++) push(CMD.row, blank);\n else push(CMD.feed, u16(FEED_AFTER));\n push(CMD.state, [0]);\n return Uint8Array.from(out);\n}\n\n/** Moves the paper by `dots`, back when negative. Models that mishandle the feed command get blank rows. */\nexport function catFeed(dots: number, model: CatModel | null): Uint8Array {\n const n = Math.abs(Math.round(dots));\n if (!n) return new Uint8Array();\n if (dots < 0) return Uint8Array.from(packet(CMD.retract, u16(n)));\n if (!model?.feedWithRows) return Uint8Array.from(packet(CMD.feed, u16(n)));\n const blank = new Uint8Array(ROW_BYTES);\n return Uint8Array.from(Array.from({ length: n }, () => packet(CMD.row, blank)).flat());\n}\n\n/** A cat printer: the shared thermal client with cat packets and flow control. */\nexport class CatPrinterClient extends ThermalPrinterClient {\n protected readonly densityLevels = ENERGY.length;\n protected readonly modelName: string;\n\n constructor(\n transport: ThermalTransport,\n readonly model: CatModel | null,\n pace: Pace = { chunkMs: 20, pauseMs: 100 },\n ) {\n super(transport, pace, 180);\n this.modelName = shownName(model, transport, 'Cat printer');\n transport.onNotify((data) => {\n if (same(data, FLOW_PAUSE)) this.paused = true;\n else if (same(data, FLOW_RESUME)) this.paused = false;\n });\n }\n\n protected encodeRows(data: ArrayLike<number>, width: number, height: number) {\n return encodeRows(data, width, height);\n }\n\n protected job(rows: Uint8Array[], density: number, copies: number, _labelType: number, gapDots?: number) {\n return printJob(rows, { density, copies, model: this.model, gapDots });\n }\n\n readonly feedsBack = true;\n\n /** These printers cannot sense the paper, so the user lines it up by hand. */\n async feedPaper(dots: number): Promise<void> {\n await this.send(catFeed(dots, this.model));\n }\n}\n\nconst same = (a: ArrayLike<number>, b: number[]) => a.length === b.length && b.every((v, i) => a[i] === v);\n","/**\n * ESC/POS mini printers: 58 mm Bluetooth receipt printers such as the MP583\n * (YAEN MPT-II). They take standard raster images (GS v 0) over the 18F0\n * printer service. See thermal.ts for what they share with other extras.\n */\nimport { modelFor, packRows, shownName, sleep, THERMAL_HEAD_DOTS, ThermalPrinterClient, type Pace, type ThermalModel, type ThermalTransport } from './thermal';\n\nexport const ESCPOS_SERVICE = '000018f0-0000-1000-8000-00805f9b34fb';\nexport const ESCPOS_WRITE = '00002af1-0000-1000-8000-00805f9b34fb';\n/** The serial service many budget printers offer, Xprinter among them; NIIMBOT printers use it too. */\nexport const ESCPOS_ALT_SERVICE = 'e7810a71-73ae-499d-8c15-faa9aef0c3f2';\nexport const ESCPOS_ALT_WRITE = 'bef8d6c9-9c21-4c9e-b632-bd58c1009f9f';\n\n/**\n * Models known to take ESC/POS raster images, by Bluetooth name. \"tested\":\n * printed with niimai on real hardware. \"high\": supported by several\n * open-source printer projects. \"medium\": the name is reported by one or two\n * of them, untested here. Names from receipt-printer projects such as\n * WebBluetoothReceiptPrinter and bitbank2/Thermal_Printer; see\n * docs/thermal-printers.md for sources. Most of these printers share one\n * Bluetooth module, so its services tell nothing apart: the name decides.\n */\n// A model's prefix is the name it shows and the key its saved settings live under, so existing entries never change it.\nexport const ESCPOS_MODELS: ThermalModel[] = [\n { prefix: 'MP583', confidence: 'tested', note: 'Sold as YAEN MPT-II' },\n { prefix: 'MPT-II', confidence: 'high', note: 'Stock module name on many 58 mm printers' },\n { prefix: 'MP58', confidence: 'medium' },\n { prefix: 'MP210', confidence: 'medium' },\n { prefix: 'MTP-II', confidence: 'high', note: 'Stock module name on many 58 mm printers' },\n { prefix: 'MTP-2', confidence: 'medium' },\n { prefix: 'MPT-3', confidence: 'medium', note: '80 mm', headDots: 576 },\n { prefix: 'MTP-3', confidence: 'medium', note: '80 mm', headDots: 576 },\n { prefix: 'PT-210', confidence: 'high', brand: 'Goojprt PT-210' },\n { prefix: 'PT210', confidence: 'medium', brand: 'Goojprt PT-210' },\n { prefix: 'GOJRPT', confidence: 'medium', brand: 'Goojprt' },\n { prefix: 'Gojprt', confidence: 'medium', brand: 'Goojprt' },\n { prefix: 'PT-200', confidence: 'medium' },\n { prefix: 'RPP02', confidence: 'high', brand: 'Rongta RPP02N', note: 'Also sold as EPPOS EP58 Plus' },\n { prefix: 'RPP300', confidence: 'medium', brand: 'Rongta RPP300', note: '80 mm', headDots: 576 },\n { prefix: 'ZJ-58', confidence: 'medium', brand: 'Zjiang' },\n { prefix: 'TM-P', confidence: 'medium', brand: 'Epson TM-P series' },\n { prefix: 'XP-P323B', confidence: 'medium', brand: 'Xprinter XP-P323B', note: '80 mm', headDots: 576 },\n { prefix: 'XP-', confidence: 'medium', brand: 'other Xprinters', generic: true },\n // Many budget printers keep their Bluetooth module's stock name: the Xprinter XP-58IIL shows as \"Printer001\".\n { prefix: 'Printer', confidence: 'tested', brand: 'Xprinter XP-58IIL, HOIN, Gprinter', note: 'Stock module name', generic: true },\n { prefix: 'printer', confidence: 'medium', note: 'Stock module name', generic: true },\n { prefix: 'BlueTooth Printer', confidence: 'medium', note: 'Stock module name: Zjiang POS-5805 and others', generic: true },\n { prefix: '58Printer', confidence: 'medium', note: 'Stock module name', generic: true },\n];\n\nexport const escposModelFor = (name: string | undefined): ThermalModel | null => modelFor(ESCPOS_MODELS, name);\n\n/** Rows per raster command; small bands keep the printer's buffer from overflowing. */\nconst BAND = 24;\n/** Dots fed between copies. */\nconst GAP_DOTS = 24;\n/** Lines fed after the last copy, so the print clears the tear bar. */\nconst FEED_LINES = 4;\n\n/** Rows for an ESC/POS printer: leftmost dot in the highest bit, centred on a head `head` dots wide. */\nexport const encodeRows = (data: ArrayLike<number>, width: number, height: number, head = THERMAL_HEAD_DOTS): Uint8Array[] =>\n packRows(data, width, height, 'msb', head);\n\n/**\n * ESC @, then each copy as GS v 0 bands with a gap between, then a feed past\n * the tear bar. With `gapDots`, for labels with gaps, every label is followed\n * by exactly that feed, so the next print starts on the next label.\n */\nexport function escposJob(rows: Uint8Array[], copies: number, gapDots?: number): Uint8Array {\n const out: number[] = [0x1b, 0x40];\n const rowBytes = rows[0]?.length ?? THERMAL_HEAD_DOTS / 8;\n for (let c = 0; c < copies; c++) {\n if (c > 0) out.push(...escposFeed(gapDots ?? GAP_DOTS));\n for (let i = 0; i < rows.length; i += BAND) {\n const band = rows.slice(i, i + BAND);\n out.push(0x1d, 0x76, 0x30, 0x00, rowBytes & 0xff, rowBytes >> 8, band.length & 0xff, band.length >> 8);\n for (const row of band) out.push(...row);\n }\n }\n if (gapDots === undefined) out.push(0x1b, 0x64, FEED_LINES);\n else out.push(...escposFeed(gapDots));\n return Uint8Array.from(out);\n}\n\n/**\n * Moves the paper forward by `dots`: line feeds with the line spacing set to\n * the distance (ESC 3 n, LF), then the default spacing back (ESC 2). The\n * MP583 ignores ESC J, the usual dot feed, but takes line feeds. These\n * printers have no reverse feed.\n */\nexport function escposFeed(dots: number): Uint8Array {\n const out: number[] = [];\n for (let left = Math.round(dots); left > 0; left -= 255) out.push(0x1b, 0x33, Math.min(255, left), 0x0a);\n if (out.length) out.push(0x1b, 0x32);\n return Uint8Array.from(out);\n}\n\n/**\n * An ESC/POS printer: the shared thermal client with raster commands. These\n * printers send no flow control, so writes are paced. Heat settings vary by\n * vendor, so darkness is left to the printer.\n */\nexport class EscPosPrinterClient extends ThermalPrinterClient {\n protected readonly densityLevels = 1;\n protected readonly modelName: string;\n protected headDots: number;\n /** A stock module name or a printer matched by its services: the head's width is a guess until set. */\n readonly headUnknown: boolean;\n\n constructor(\n transport: ThermalTransport,\n readonly model: ThermalModel | null,\n pace: Pace = { chunkMs: 30, pauseMs: 100, resetMs: 300 },\n ) {\n super(transport, pace, 100);\n this.modelName = shownName(model, transport, 'Thermal printer');\n this.headDots = model?.headDots ?? THERMAL_HEAD_DOTS;\n this.headUnknown = !!model?.generic && model.headDots === undefined;\n }\n\n /** Sets the head's width, in whole bytes of dots, on a printer whose width is not known; a known head keeps its own. */\n setHeadDots(dots: number) {\n if (this.headUnknown) this.headDots = Math.max(8, Math.round(dots / 8) * 8);\n }\n\n protected encodeRows(data: ArrayLike<number>, width: number, height: number) {\n return encodeRows(data, width, height, this.headDots);\n }\n\n protected job(rows: Uint8Array[], _density: number, copies: number, _labelType: number, gapDots?: number) {\n return escposJob(rows, copies, gapDots);\n }\n\n /**\n * A job starts with ESC @. Some printers drop what arrives while they\n * reset, losing the top of the label (the MTP-4B lost its first band), so\n * the reset goes on its own and the rest follows a moment later. The bytes\n * are the same either way.\n */\n override async send(bytes: Uint8Array, onSent?: (share: number) => void): Promise<void> {\n if (!this.pace.resetMs || bytes.length <= 2 || bytes[0] !== 0x1b || bytes[1] !== 0x40) return super.send(bytes, onSent);\n await super.send(bytes.subarray(0, 2));\n await sleep(this.pace.resetMs);\n return super.send(bytes.subarray(2), onSent);\n }\n\n /** These printers cannot sense the paper, so the user lines it up by hand. */\n async feedPaper(dots: number): Promise<void> {\n if (dots < 0) throw new Error('This printer cannot move the paper back');\n await this.send(escposFeed(dots));\n }\n}\n","/**\n * TSPL label printers: 2- and 3-inch desktop label printers that take TSC's\n * label language over the 18F0 printer service. A job is a few text commands\n * (SIZE, GAP, DENSITY, CLS) around one BITMAP of the page, then PRINT. The\n * printer finds each label's edge itself. It answers \"OK\" on 2AF0 when it has\n * taken a job, and a status byte to ESC ! ?.\n */\nimport type { LabelSize } from './profiles';\nimport { modelFor, shownName, ThermalPrinterClient, type Pace, type ThermalModel, type ThermalTransport } from './thermal';\n\nexport const TSPL_SERVICE = '000018f0-0000-1000-8000-00805f9b34fb';\nexport const TSPL_WRITE = '00002af1-0000-1000-8000-00805f9b34fb';\nexport const TSPL_NOTIFY = '00002af0-0000-1000-8000-00805f9b34fb';\n\n/** Dots across the head: 80 mm at 203 dpi. */\nexport const TSPL_HEAD_DOTS = 640;\nconst DOTS_PER_MM = 8;\n\n/**\n * Models known to take TSPL over 18F0. \"tested\": printed with niimai on real\n * hardware. \"medium\": reported by open-source printer projects, untested here.\n */\nexport const TSPL_MODELS: ThermalModel[] = [\n { prefix: 'QR380A', confidence: 'tested', note: 'Sold as CC390', usbIds: [[0x28e9, 0x0289]] },\n { prefix: 'GP-M322', confidence: 'medium', brand: 'Gprinter GP-M322', headDots: 576 },\n];\n\nexport const tsplModelFor = (name: string | undefined): ThermalModel | null => modelFor(TSPL_MODELS, name);\n\n/** Common 2- to 3-inch label sizes, width × height in mm, smallest first. */\nexport const TSPL_SIZES: LabelSize[] = [\n [40, 30],\n [50, 25],\n [50, 30],\n [50, 40],\n [57, 32],\n [60, 40],\n [50, 50],\n [70, 50],\n [75, 50],\n [80, 50],\n [60, 60],\n [80, 60],\n [80, 80],\n [75, 100],\n].map(([widthMm, heightMm]) => ({ widthMm, heightMm }));\n\n/** TSPL's darkness steps, 0 to 15; niimai offers 1 to 15. */\nconst DENSITY_LEVELS = 15;\n/** The gap between labels, for printers that look for it. */\nconst GAP_MM = 2;\n\n/** NIIMBOT label types, as the print dialog passes them. */\nconst LABEL_TYPE = { blackmark: 2, continuous: 3 } as const;\n\n/** The status query, and the bits of its answer that stop a print. */\nexport const STATUS_QUERY = [0x1b, 0x21, 0x3f];\nconst STATUS_FAULTS: [number, string][] = [\n [0x01, 'Printer cover is open'],\n [0x02, 'Paper jam'],\n [0x04, 'Out of paper'],\n [0x08, 'Out of ribbon'],\n];\n\n/** Replies that mean a job was taken: \"OK\". */\nconst OK = [0x4f, 0x4b];\n/** How long to wait for \"OK\" before asking the printer what is wrong. */\nconst ACK_TIMEOUT_MS = 20_000;\n/** How long to wait for the answer to a status query. */\nconst STATUS_TIMEOUT_MS = 2_000;\n\n/**\n * Rows for a TSPL BITMAP: as wide as the page, leftmost dot in the highest\n * bit, and 0 for a dark dot.\n */\nexport function encodeRows(data: ArrayLike<number>, width: number, height: number): Uint8Array[] {\n const bytes = Math.ceil(width / 8);\n const rows: Uint8Array[] = [];\n for (let y = 0; y < height; y++) {\n const row = new Uint8Array(bytes).fill(0xff);\n for (let x = 0; x < width; x++) if (data[(y * width + x) * 4] < 128) row[x >> 3] &= ~(0x80 >> (x & 7));\n rows.push(row);\n }\n return rows;\n}\n\nconst ascii = (s: string) => Array.from(s, (c) => c.charCodeAt(0));\nconst mm = (dots: number) => `${Math.round((dots / DOTS_PER_MM) * 10) / 10} mm`;\n\n/** How the printer finds the start of each label. */\nfunction paperCommand(labelType: number): string {\n if (labelType === LABEL_TYPE.continuous) return 'GAP 0 mm,0 mm';\n if (labelType === LABEL_TYPE.blackmark) return `BLINE ${GAP_MM} mm,0 mm`;\n return `GAP ${GAP_MM} mm,0 mm`;\n}\n\n/** One label: setup, the page as one bitmap at the top left, then print it `copies` times. */\nexport function tsplJob(rows: Uint8Array[], { density, copies, labelType }: { density: number; copies: number; labelType: number }): Uint8Array {\n const bytes = rows[0]?.length ?? 0;\n const height = rows.length;\n const head = [\n `SIZE ${mm(bytes * 8)},${mm(height)}`,\n paperCommand(labelType),\n 'DIRECTION 0',\n `DENSITY ${Math.min(DENSITY_LEVELS, Math.max(0, Math.round(density)))}`,\n 'CLS',\n ];\n const out = ascii(head.map((l) => `${l}\\r\\n`).join('') + `BITMAP 0,0,${bytes},${height},0,`);\n for (const row of rows) for (const b of row) out.push(b);\n out.push(...ascii(`\\r\\nPRINT ${Math.max(1, copies)},1\\r\\n`));\n return Uint8Array.from(out);\n}\n\n/**\n * A TSPL label printer: the shared thermal client with TSPL jobs. It shows\n * as a label printer (roll sizes, paper types). Writes are paced at the size\n * these printers' BLE modules take without dropping data.\n */\nexport class TsplPrinterClient extends ThermalPrinterClient {\n protected readonly densityLevels = DENSITY_LEVELS;\n protected readonly modelName: string;\n protected readonly headDots: number;\n protected readonly kind = undefined;\n protected readonly labelSizes = TSPL_SIZES;\n\n private oks = 0;\n private status: number | null = null;\n private wake: (() => void) | null = null;\n\n constructor(\n transport: ThermalTransport,\n readonly model: ThermalModel | null,\n pace: Pace = { chunkMs: 30, pauseMs: 100 },\n private ackTimeoutMs = ACK_TIMEOUT_MS,\n ) {\n super(transport, pace, 100);\n this.modelName = shownName(model, transport, 'Label printer');\n this.headDots = model?.headDots ?? TSPL_HEAD_DOTS;\n transport.onNotify((data) => {\n if (data.length >= 2 && data[0] === OK[0] && data[1] === OK[1]) this.oks++;\n else if (data.length === 1) this.status = data[0];\n else return;\n this.wake?.();\n });\n }\n\n protected encodeRows(data: ArrayLike<number>, width: number, height: number) {\n return encodeRows(data, width, height);\n }\n\n /** These printers cannot sense the roll; its size is remembered per printer. */\n rollKey() {\n return `printer:${this.transport.name || this.modelName}`;\n }\n\n protected job(rows: Uint8Array[], density: number, copies: number, labelType: number) {\n this.oks = 0;\n return tsplJob(rows, { density, copies, labelType });\n }\n\n /** Resolves when `done` holds or the time runs out. */\n private async until(done: () => boolean, ms: number) {\n const deadline = Date.now() + ms;\n while (!done() && Date.now() < deadline) {\n await new Promise<void>((resolve) => {\n const t = setTimeout(resolve, deadline - Date.now());\n this.wake = () => (clearTimeout(t), resolve());\n });\n }\n this.wake = null;\n }\n\n /**\n * Waits for the printer's \"OK\", so the next label never lands on a busy\n * printer. Without one, asks for its status: a fault fails the print, and a\n * printer that is merely quiet is left to carry on.\n */\n protected async afterPage() {\n await this.until(() => this.oks > 0, this.ackTimeoutMs);\n if (this.oks > 0) return;\n this.status = null;\n await this.send(Uint8Array.from(STATUS_QUERY));\n await this.until(() => this.status !== null, STATUS_TIMEOUT_MS);\n const fault = STATUS_FAULTS.find(([bit]) => (this.status ?? 0) & bit);\n if (fault) throw new Error(fault[1]);\n console.warn('[niimai] Label printer did not confirm the job');\n }\n}\n","/**\n * Every printer outside NIIMBOT that libniimai knows, by Bluetooth name, in\n * one lookup across families. A name matches one family at most, and a name\n * that matches none is known to be unknown before anything is sent to it.\n * The models themselves live with their family: ESCPOS_MODELS, TSPL_MODELS\n * and CAT_MODELS.\n */\nimport type { ClientLike } from './client';\nimport { CAT_MODELS } from './cat';\nimport { ESCPOS_MODELS, EscPosPrinterClient } from './escpos';\nimport { THERMAL_HEAD_DOTS, type Pace, type ThermalModel, type ThermalTransport } from './thermal';\nimport { TSPL_HEAD_DOTS, TSPL_MODELS, TsplPrinterClient } from './tspl';\n\nexport type PrinterFamily = 'escpos' | 'tspl' | 'cat';\n\nexport interface CatalogEntry {\n family: PrinterFamily;\n model: ThermalModel;\n}\n\nexport const PRINTER_CATALOG: CatalogEntry[] = [\n ...ESCPOS_MODELS.map((model) => ({ family: 'escpos' as const, model })),\n ...TSPL_MODELS.map((model) => ({ family: 'tspl' as const, model })),\n ...CAT_MODELS.map((model) => ({ family: 'cat' as const, model })),\n];\n\n/** The known printer a Bluetooth name belongs to, longest prefix first across every family. */\nexport function identifyPrinter(name: string | undefined): CatalogEntry | null {\n if (!name) return null;\n let best: CatalogEntry | null = null;\n for (const entry of PRINTER_CATALOG) {\n if (name.startsWith(entry.model.prefix) && entry.model.prefix.length > (best?.model.prefix.length ?? 0)) best = entry;\n }\n return best;\n}\n\n/** The known printer behind a USB device: by its vendor and product IDs, then by its product name. */\nexport function identifyUsbPrinter({ vendorId, productId, productName }: { vendorId: number; productId: number; productName?: string }): CatalogEntry | null {\n return PRINTER_CATALOG.find((e) => e.model.usbIds?.some(([v, p]) => v === vendorId && p === productId)) ?? identifyPrinter(productName);\n}\n\n/**\n * The client for a thermal printer over a cable link. Cat printers are\n * Bluetooth only, so they get null. `ackTimeoutMs`: how long a label printer\n * waits for its \"OK\" before asking for its status.\n */\nexport function cableClient({ family, model }: CatalogEntry, transport: ThermalTransport, pace: Pace, ackTimeoutMs: number): ClientLike | null {\n if (family === 'tspl') return new TsplPrinterClient(transport, model, pace, ackTimeoutMs);\n if (family === 'escpos') return new EscPosPrinterClient(transport, model, pace);\n return null;\n}\n\n/** A kind of printer to pick for one niimai does not recognise. */\nexport interface PrinterType extends CatalogEntry {\n id: string;\n name: string;\n}\n\n/** The kinds of printer an unrecognised one can be set to, alongside NIIMBOT's MANUAL_PROFILES. */\nexport const PRINTER_TYPES: PrinterType[] = [\n { id: 'escpos-58', name: 'Receipt printer, 58 mm', family: 'escpos', model: { prefix: '', confidence: 'medium', generic: true, headDots: THERMAL_HEAD_DOTS } },\n { id: 'escpos-80', name: 'Receipt printer, 80 mm', family: 'escpos', model: { prefix: '', confidence: 'medium', generic: true, headDots: 576 } },\n { id: 'tspl', name: 'Label printer (TSPL)', family: 'tspl', model: { prefix: '', confidence: 'medium', generic: true, headDots: TSPL_HEAD_DOTS } },\n { id: 'cat', name: 'Cat printer', family: 'cat', model: { prefix: '', confidence: 'medium', generic: true } },\n];\n\nexport const findPrinterType = (id: string | null | undefined): PrinterType | null => PRINTER_TYPES.find((t) => t.id === id) ?? null;\n","import type { ClientLike } from './client';\n\n/**\n * A client that picks the real client when it connects, then passes\n * everything through to it: the Bluetooth and USB printers behind the\n * Connect buttons.\n */\nexport abstract class RoutedClient implements ClientLike {\n protected inner: ClientLike | null = null;\n\n abstract connect(): Promise<{ deviceName?: string }>;\n\n protected get client(): ClientLike {\n if (!this.inner) throw new Error('Not connected');\n return this.inner;\n }\n\n async disconnect() {\n await this.inner?.disconnect();\n }\n\n isConnected() {\n return !!this.inner?.isConnected();\n }\n\n on(event: string, fn: (e: any) => void) {\n return this.client.on(event, fn);\n }\n\n off(event: string, fn: (e: any) => void) {\n return this.inner?.off(event, fn);\n }\n\n getPrinterInfo() {\n return this.inner?.getPrinterInfo() ?? {};\n }\n\n getModelMetadata() {\n return this.inner?.getModelMetadata();\n }\n\n getPrintTaskType() {\n return this.inner?.getPrintTaskType();\n }\n\n getRfidInfo() {\n return this.inner?.getRfidInfo() ?? {};\n }\n\n fetchRfidInfo() {\n return this.client.fetchRfidInfo();\n }\n\n startHeartbeat() {\n this.inner?.startHeartbeat();\n }\n\n stopHeartbeat() {\n this.inner?.stopHeartbeat();\n }\n\n isHeartbeatStarted() {\n return !!this.inner?.isHeartbeatStarted?.();\n }\n\n get protocol() {\n return this.client.protocol;\n }\n\n get encodePage() {\n return this.inner?.encodePage?.bind(this.inner);\n }\n\n get rollKey() {\n return this.inner?.rollKey?.bind(this.inner);\n }\n\n get feedPaper() {\n return this.inner?.feedPaper?.bind(this.inner);\n }\n\n get headUnknown() {\n return this.inner?.headUnknown;\n }\n\n get setHeadDots() {\n return this.inner?.setHeadDots?.bind(this.inner);\n }\n\n get feedsBack() {\n return this.inner?.feedsBack;\n }\n}\n","import { CANDIDATE_SERVICES, createBleClient, requestPrinter } from './ble';\nimport { findPrinterType, identifyPrinter, PRINTER_CATALOG, type CatalogEntry, type PrinterFamily, type PrinterType } from './catalog';\nimport { CAT_RX, CAT_SERVICE, CAT_TX, CatPrinterClient } from './cat';\nimport { ESCPOS_ALT_SERVICE, ESCPOS_ALT_WRITE, ESCPOS_SERVICE, ESCPOS_WRITE, EscPosPrinterClient } from './escpos';\nimport { TSPL_NOTIFY, TSPL_SERVICE, TSPL_WRITE, TsplPrinterClient } from './tspl';\nimport { webTransport } from './thermal';\nimport type { ClientLike } from './client';\nimport { RoutedClient } from './routed';\n\ntype Lib = typeof import('@mmote/niimbluelib');\n\n/**\n * A writable characteristic on one of the serial services budget printers\n * offer, for ESC/POS printers on none of the known layouts.\n */\nasync function writeChannel(server: BluetoothRemoteGATTServer, services: string[]): Promise<{ service: string; write: string } | null> {\n for (const service of CANDIDATE_SERVICES) {\n if (!services.includes(service) || service === CAT_SERVICE) continue;\n const chars = await server\n .getPrimaryService(service)\n .then((s) => s.getCharacteristics())\n .catch(() => []);\n const write = chars.find((c) => c.properties.writeWithoutResponse || c.properties.write);\n if (write) return { service, write: write.uuid };\n }\n return null;\n}\n\nconst PRINTER_TYPE_NAMES: Record<PrinterFamily, string> = { escpos: 'receipt printer', tspl: 'label printer', cat: 'cat printer' };\n\n/** The client for a known or hand-picked thermal printer, over the service its family uses; null when the printer lacks it. */\nasync function thermalClient(device: BluetoothDevice, server: BluetoothRemoteGATTServer, services: string[], { family, model }: CatalogEntry): Promise<ClientLike | null> {\n if (family === 'cat') {\n return services.includes(CAT_SERVICE) ? new CatPrinterClient(await webTransport(device, server, { service: CAT_SERVICE, write: CAT_TX, notify: CAT_RX }), model) : null;\n }\n if (family === 'tspl') {\n return services.includes(TSPL_SERVICE) ? new TsplPrinterClient(await webTransport(device, server, { service: TSPL_SERVICE, write: TSPL_WRITE, notify: TSPL_NOTIFY }), model) : null;\n }\n const fixed = services.includes(ESCPOS_SERVICE)\n ? { service: ESCPOS_SERVICE, write: ESCPOS_WRITE }\n : services.includes(ESCPOS_ALT_SERVICE)\n ? { service: ESCPOS_ALT_SERVICE, write: ESCPOS_ALT_WRITE }\n : null;\n // A printer on a known service may still write through another characteristic: any writable one will do.\n const transport = fixed && (await webTransport(device, server, fixed).catch(() => null));\n if (transport) return new EscPosPrinterClient(transport, model);\n const channel = await writeChannel(server, services);\n return channel ? new EscPosPrinterClient(await webTransport(device, server, channel), model) : null;\n}\n\n/** NIIMBOT printers offer one of these; a printer with neither cannot be one. */\nconst NIIMBOT_SERVICES = CANDIDATE_SERVICES.slice(0, 2);\n/** A receipt printer matched by its services: its head's width is unknown until the paper says. */\nconst RECEIPT: CatalogEntry = { family: 'escpos', model: { prefix: '', confidence: 'medium', generic: true } };\nconst CAT = findPrinterType('cat')!;\n\n/**\n * What an unknown printer that did not answer as a NIIMBOT is, by its\n * services: the cat service makes it a cat printer, and any other serial\n * service outside NIIMBOT's a receipt printer. Null when it offers only\n * NIIMBOT services, or nothing to print through: those stay NIIMBOT's.\n */\nexport function byServices(services: string[]): CatalogEntry | null {\n if (services.includes(CAT_SERVICE)) return CAT;\n return services.some((s) => CANDIDATE_SERVICES.includes(s) && !NIIMBOT_SERVICES.includes(s)) ? RECEIPT : null;\n}\n\n/** A family a printer was matched as by its services. */\nexport type MatchedFamily = 'escpos' | 'cat';\n/** Printers matched by their services, by Bluetooth name. */\nexport type MatchedPrinters = Record<string, MatchedFamily>;\n\n/**\n * Clears out the NIIMBOT tries a receipt printer took as text: FS . leaves\n * Chinese character mode, so no half character swallows what follows; two\n * line feeds print what is left in the buffer, even after a dangling byte;\n * ESC @ resets the printer.\n */\nconst FLUSH = Uint8Array.from([0x1c, 0x2e, 0x0a, 0x0a, 0x1b, 0x40]);\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\n/** The printer's GATT server again after a failed attempt dropped the link, giving it a moment between tries. */\nasync function reconnect(device: BluetoothDevice): Promise<BluetoothRemoteGATTServer | null> {\n for (let i = 1; i <= 3; i++) {\n await sleep(RETRY_MS * i);\n const server = await device.gatt?.connect().catch(() => null);\n if (server) return server;\n }\n return null;\n}\n\n/** The pause before another try, growing with each one. */\nexport const RETRY_MS = 250;\n\n/** The name starts like one of niimbluelib's NIIMBOT models (\"D110-…\", \"B21_…\"). */\nfunction looksNiimbot(lib: Lib, name: string | undefined): boolean {\n const token = name?.split(/[-_ ]/)[0].toUpperCase();\n return !!token && !!lib.modelsLibrary?.some((m) => m.model.split('_')[0] === token);\n}\n\n/**\n * The Bluetooth printer behind the Connect button. One chooser lists NIIMBOT\n * printers and the known extra thermal printers. A known cat printer offering\n * AE30 gets the cat client; a known ESC/POS printer the ESC/POS client, over\n * 18F0, the shared E781 service (as Xprinters use), or any serial service\n * with a writable characteristic; a known TSPL label printer offering 18F0\n * the TSPL client; and everything else the NIIMBOT client exactly as before.\n * Known names never overlap, so each printer matches one client at most.\n */\nexport class BluetoothPrinter extends RoutedClient {\n constructor(private lib: () => Promise<Lib>) {\n super();\n }\n\n /** The kind of printer to treat an unrecognised one as; null leaves it to NIIMBOT. */\n private fallback: PrinterType | null = null;\n\n unrecognisedAs(typeId: string | null) {\n this.fallback = findPrinterType(typeId);\n }\n\n /** Printers matched by their services before, by Bluetooth name: they skip the NIIMBOT handshake. */\n private remembered: MatchedPrinters = {};\n /** What the printer connected now was matched as by its services, if it was. */\n matchedAs: MatchedFamily | undefined;\n\n rememberMatches(matches: MatchedPrinters) {\n this.remembered = matches;\n }\n\n async connect() {\n this.matchedAs = undefined;\n // With a type picked for unrecognised printers, the chooser lists every device, whatever its name.\n const device = await requestPrinter(\n PRINTER_CATALOG.map((e) => e.model.prefix),\n !!this.fallback,\n );\n const known = identifyPrinter(device.name);\n // A hand-picked type is only for printers that are neither known nor named like a NIIMBOT: a NIIMBOT always keeps its own client.\n const fallback = !known && this.fallback && !looksNiimbot(await this.lib(), device.name) ? this.fallback : null;\n const entry = known ?? fallback;\n if (entry && device.gatt) {\n const server = await device.gatt.connect();\n const services = (await server.getPrimaryServices().catch(() => [])).map((s) => s.uuid);\n this.inner = await thermalClient(device, server, services, entry);\n if (this.inner) return this.inner.connect();\n // A printer set to a type by hand that lacks its service should say so, not fall to NIIMBOT.\n if (!known) throw new Error(`${device.name ?? 'This printer'} does not offer a ${PRINTER_TYPE_NAMES[entry.family]} service`);\n }\n const lib = await this.lib();\n // Matched by its services before, after NIIMBOT failed it twice: it goes straight to that client, sending no NIIMBOT bytes it would print.\n const before = !entry && device.name && !looksNiimbot(lib, device.name) ? this.remembered[device.name] : undefined;\n if (before && device.gatt) {\n const server = await device.gatt.connect();\n const services = (await server.getPrimaryServices().catch(() => [])).map((s) => s.uuid);\n const client = await thermalClient(device, server, services, before === 'cat' ? CAT : RECEIPT).catch(() => null);\n if (client) {\n this.inner = client;\n this.matchedAs = before;\n return client.connect();\n }\n }\n this.inner = createBleClient(lib, device) as unknown as ClientLike;\n try {\n return await this.inner.connect();\n } catch (err) {\n // A NIIMBOT by name, or a known printer missing its service, keeps its own error, exactly as before.\n if (looksNiimbot(lib, device.name) || entry) throw err;\n return this.unknown(device, err);\n }\n }\n\n /**\n * An unknown name that failed its first NIIMBOT try. NIIMBOT goes first\n * again, so an unknown NIIMBOT that missed one handshake still gets its\n * own client; only after a second miss do its services decide, and a\n * printer offering only NIIMBOT services is never handed to another client.\n */\n private async unknown(device: BluetoothDevice, first: unknown) {\n await sleep(RETRY_MS);\n try {\n return await this.inner!.connect();\n } catch {\n // Still no NIIMBOT answer: what it offers says what else it is.\n }\n const server = await reconnect(device);\n const services = server ? (await server.getPrimaryServices().catch(() => [])).map((s) => s.uuid) : [];\n console.info('[niimai] services', device.name, services);\n const guess = byServices(services);\n const client = server && guess ? await thermalClient(device, server, services, guess).catch(() => null) : null;\n if (client) {\n this.inner = client;\n const info = await client.connect();\n this.matchedAs = guess!.family === 'cat' ? 'cat' : 'escpos';\n // The NIIMBOT tries sat in its buffer as text: print them out now, apart from the first label.\n if (this.matchedAs === 'escpos') await (client as unknown as { send(b: Uint8Array): Promise<void> }).send(FLUSH).catch(() => {});\n return info;\n }\n console.warn('[niimai] connect failed', device.name, first);\n if (!services.length) throw new Error(`${device.name ?? 'This printer'} offers no print service niimai knows. See Help → Printer not recognised.`);\n throw new Error(`Could not connect to ${device.name ?? 'this printer'}. Pick its type under Advanced and connect again.`);\n }\n}\n","import type { PrinterModelMeta } from '@mmote/niimbluelib';\nimport type { ClientLike, PrintTaskLike } from './client';\n\ntype Listener = (e: any) => void;\n\n/** A printer that only exists in memory. Used by the demo mode and the tests. */\nexport class MockClient implements ClientLike {\n connected = false;\n printed: { image: unknown; quantity: number; heartbeat: boolean }[] = [];\n failNextPrint: string | null = null;\n private listeners = new Map<string, Set<Listener>>();\n\n /** Pass null to act as a model the library does not recognise. */\n constructor(private meta: PrinterModelMeta | null = DEMO_META) {}\n\n async connect() {\n this.connected = true;\n this.emit('connect', { info: { deviceName: 'Demo printer' } });\n return { deviceName: 'Demo printer' };\n }\n async disconnect() {\n this.connected = false;\n this.emit('disconnect', {});\n }\n isConnected() {\n return this.connected;\n }\n on(event: string, fn: Listener) {\n if (!this.listeners.has(event)) this.listeners.set(event, new Set());\n this.listeners.get(event)!.add(fn);\n return this;\n }\n off(event: string, fn: Listener) {\n this.listeners.get(event)?.delete(fn);\n return this;\n }\n emit(event: string, e: unknown) {\n this.listeners.get(event)?.forEach((fn) => fn(e));\n }\n protocolVersion = 1;\n getPrinterInfo() {\n return { batteryPercents: 80, modelId: 512, printheadWidth: 96, protocolVersion: this.protocolVersion };\n }\n getModelMetadata() {\n return this.meta ?? undefined;\n }\n getPrintTaskType() {\n return this.meta ? ('D11_V1' as const) : undefined;\n }\n /** Set false to act like printers that report only the roll's barcode. */\n reportsPaperSize = true;\n\n getRfidInfo() {\n return {\n labelRfidInfo: { tagPresent: true, barCode: '6971501227941', allPaper: 210, usedPaper: 12, consumablesType: 1 },\n paperInfo: this.reportsPaperSize ? { valid: true, paperWidth: 40, paperHeight: 12 } : undefined,\n };\n }\n async fetchRfidInfo() {\n return this.getRfidInfo();\n }\n startHeartbeat() {\n this.heartbeat = true;\n this.emit('heartbeat', { data: { batteryPercents: 80, lidClosed: true, paperInserted: true } });\n }\n heartbeat = false;\n stopHeartbeat() {\n this.heartbeat = false;\n }\n /** How many \"print end\" polls answer false before the printer reports done. */\n printEndPending = 0;\n protocol = {\n printEnd: async () => {\n if (this.printEndPending > 0) {\n this.printEndPending--;\n return false;\n }\n return true;\n },\n newPrintTask: (_name: string, options: Record<string, unknown>): PrintTaskLike => {\n const total = Number(options.totalPages ?? 1);\n return {\n printInit: async () => {\n if (this.failNextPrint) {\n const msg = this.failNextPrint;\n this.failNextPrint = null;\n throw new Error(msg);\n }\n },\n printPage: async (image, quantity = 1) => {\n this.printed.push({ image, quantity, heartbeat: this.heartbeat });\n },\n waitForFinished: async () => {\n for (let page = 1; page <= total; page++) {\n this.emit('printprogress', { page, pagesTotal: total, pagePrintProgress: 100, pageFeedProgress: 100 });\n }\n },\n printEnd: async () => true,\n };\n },\n };\n}\n\nexport const DEMO_META = {\n model: 'D11',\n id: [512],\n dpi: 203,\n printDirection: 'left',\n printheadPixels: 96,\n paperTypes: [1],\n densityMin: 1,\n densityMax: 3,\n densityDefault: 2,\n} as unknown as PrinterModelMeta;\n","/**\n * Paper types, as niimbluelib's LabelType numbers. The printer uses this to\n * find the start of each label, so the wrong one mis-feeds the roll.\n */\nexport type PaperType = 'gaps' | 'blackmark' | 'continuous' | 'perforated' | 'transparent';\n\nexport const PAPER_TYPES: { value: PaperType; label: string; labelType: number }[] = [\n { value: 'gaps', label: 'Labels with gaps', labelType: 1 },\n { value: 'blackmark', label: 'Black mark', labelType: 2 },\n { value: 'continuous', label: 'Continuous', labelType: 3 },\n { value: 'perforated', label: 'Perforated', labelType: 4 },\n { value: 'transparent', label: 'Transparent', labelType: 5 },\n];\n\nexport const paperFromLabelType = (n: number | undefined): PaperType | undefined => PAPER_TYPES.find((p) => p.labelType === n)?.value;\n\nexport const labelTypeOf = (p: PaperType): number => PAPER_TYPES.find((x) => x.value === p)!.labelType;\n\nexport const paperName = (p: PaperType): string => PAPER_TYPES.find((x) => x.value === p)!.label;\n\n/** The paper to print on: the label's own choice, then what the roll reports, then labels with gaps. */\nexport const effectivePaper = (chosen: PaperType | undefined, fromRoll: PaperType | undefined): PaperType => chosen ?? fromRoll ?? 'gaps';\n","import type { PrinterModelMeta, PrintTaskName } from '@mmote/niimbluelib';\n\n/** A label's size in millimetres. */\nexport interface LabelSize {\n widthMm: number;\n heightMm: number;\n}\n\nexport type PrintDirection = 'left' | 'top';\n\n/** Everything the app needs to know to render and send a page to a printer. */\nexport interface PrinterProfile {\n id: string;\n name: string;\n dpi: number;\n printheadPixels: number;\n printDirection: PrintDirection;\n printTask: PrintTaskName;\n densityMin: number;\n densityMax: number;\n densityDefault: number;\n /** Extra thermal printers (cat, ESC/POS): continuous paper, no roll tags. Unset for NIIMBOT. */\n kind?: 'thermal';\n /** Label sizes common for this printer (TSPL label printers). Unset for NIIMBOT, whose sizes come from its rolls. */\n sizes?: LabelSize[];\n}\n\n/** Manual profiles for printers niimbluelib does not recognise yet. Each mirrors a known model family. */\nexport const MANUAL_PROFILES: PrinterProfile[] = [\n { id: 'd11', name: 'D11 family', dpi: 203, printheadPixels: 96, printDirection: 'left', printTask: 'D11_V1', densityMin: 1, densityMax: 3, densityDefault: 2 },\n { id: 'd110', name: 'D110 family', dpi: 203, printheadPixels: 96, printDirection: 'left', printTask: 'D110', densityMin: 1, densityMax: 3, densityDefault: 2 },\n { id: 'b1', name: 'B1 family', dpi: 203, printheadPixels: 384, printDirection: 'top', printTask: 'B1', densityMin: 1, densityMax: 5, densityDefault: 3 },\n { id: 'b21', name: 'B21 family', dpi: 203, printheadPixels: 384, printDirection: 'top', printTask: 'B21_V1', densityMin: 1, densityMax: 5, densityDefault: 3 },\n];\n\nexport const findManualProfile = (id: string | null | undefined): PrinterProfile | undefined =>\n MANUAL_PROFILES.find((p) => p.id === id);\n\n/**\n * Print tasks verified on hardware where niimbluelib's choice prints blank.\n * A B21 C2B on protocol 0 needs the original B21 packet formats; the library\n * gives every B21 C2B the B1 task, which that firmware accepts but never prints.\n */\nconst TASK_QUIRKS: { model: string; maxProtocol: number; task: PrintTaskName }[] = [{ model: 'B21_C2B', maxProtocol: 0, task: 'B21_V1' }];\n\nexport function correctTask(model: string, protocolVersion: number | undefined, task: PrintTaskName | undefined): PrintTaskName | undefined {\n const quirk = TASK_QUIRKS.find((q) => q.model === model && (protocolVersion ?? 0) <= q.maxProtocol);\n return quirk?.task ?? task;\n}\n\n/**\n * The profile to print with: the library's model data when it recognises the\n * printer, otherwise the manual choice. A detected model always wins, so a\n * choice left over from another printer cannot send the wrong settings.\n */\nexport function resolveProfile(\n meta: PrinterModelMeta | undefined,\n libraryTask: PrintTaskName | undefined,\n manual?: PrinterProfile,\n protocolVersion?: number,\n): PrinterProfile | null {\n const task = meta ? correctTask(meta.model, protocolVersion, libraryTask) : undefined;\n if (!meta || !task) return manual ?? null;\n return {\n id: `auto:${meta.model}`,\n name: meta.model.replace(/_/g, ' '),\n dpi: meta.dpi,\n printheadPixels: meta.printheadPixels,\n printDirection: meta.printDirection,\n printTask: task,\n densityMin: meta.densityMin,\n densityMax: meta.densityMax,\n densityDefault: meta.densityDefault,\n ...((meta as { kind?: string }).kind === 'thermal' ? { kind: 'thermal' as const } : {}),\n ...((meta as { labelSizes?: LabelSize[] }).labelSizes ? { sizes: (meta as { labelSizes?: LabelSize[] }).labelSizes } : {}),\n };\n}\n\n/** Widest label edge the printhead covers, in millimetres. */\nexport const printheadMm = (p: PrinterProfile): number => (p.printheadPixels / p.dpi) * 25.4;\n\n/**\n * The part of a page the printhead can print, centred across the head. Pages\n * wider than the head are cropped: printers discard rows longer than the head.\n */\nexport function printheadCrop(width: number, height: number, p: Pick<PrinterProfile, 'printheadPixels' | 'printDirection'>) {\n const acrossIsWidth = p.printDirection === 'top';\n const across = acrossIsWidth ? width : height;\n const keep = Math.min(across, p.printheadPixels);\n const offset = Math.floor((across - keep) / 2);\n return acrossIsWidth\n ? { x: offset, y: 0, width: keep, height, cropped: keep < across }\n : { x: 0, y: offset, width, height: keep, cropped: keep < across };\n}\n\n/**\n * True when any black pixel of a page (RGBA, as from getImageData) falls\n * outside what the printhead covers, so part of the design would not print.\n */\nexport function inkOutsideHead(data: ArrayLike<number>, width: number, height: number, p: Pick<PrinterProfile, 'printheadPixels' | 'printDirection'>): boolean {\n const crop = printheadCrop(width, height, p);\n if (!crop.cropped) return false;\n for (let y = 0; y < height; y++) {\n const insideRow = y >= crop.y && y < crop.y + crop.height;\n for (let x = 0; x < width; x++) {\n if (insideRow && x >= crop.x && x < crop.x + crop.width) {\n x = crop.x + crop.width - 1;\n continue;\n }\n if (data[(y * width + x) * 4] < 128) return true;\n }\n }\n return false;\n}\n","import type { ClientLike, ConnectionKind, PrintTaskLike } from './client';\nimport { paperFromLabelType, type PaperType } from './paper';\nimport { findManualProfile, printheadCrop, resolveProfile, type PrinterProfile } from './profiles';\n\nexport type PrinterState = 'disconnected' | 'connecting' | 'ready' | 'printing';\n\nexport interface PrinterStatus {\n state: PrinterState;\n deviceName?: string;\n battery?: number;\n /** Null when the printer is unknown and no manual profile is chosen. */\n profile: PrinterProfile | null;\n detectedModel?: string;\n /**\n * The loaded roll. Size comes from the printer's paper info where it reports\n * one; the barcode identifies the roll when it does not (the B21 C2B, for one).\n */\n label?: { widthMm?: number; heightMm?: number; remaining?: number; capacity?: number; barcode?: string; paper?: PaperType; manual?: boolean };\n lidClosed?: boolean;\n paperInserted?: boolean;\n /** Pages done of the total; `sending` is how much of the current page has gone over the link, 0–100, for printers that report it. */\n progress?: { page: number; total: number; sending?: number };\n /** The paper can be moved by hand with `feed()`, on printers that cannot sense it. */\n canFeed?: boolean;\n /** `feed()` can also move the paper back. */\n canRetract?: boolean;\n /** The head's width is a guess: `setPrintWidth()` sets it, from the paper loaded. */\n headUnknown?: boolean;\n /** The printer was matched by its services, as this family: keep it with `rememberMatches()` so it skips the NIIMBOT handshake next time. */\n matchedAs?: 'escpos' | 'cat';\n}\n\n/** Nudges the printed image, in mm: positive x moves right, positive y moves down. */\nexport interface PrintOffset {\n x: number;\n y: number;\n}\n\nexport interface PrintOptions {\n density: number;\n /** Copies of each page. */\n copies: number;\n labelType?: number;\n offset?: PrintOffset;\n /**\n * Labels with gaps on a printer that cannot sense them (`status.canFeed`):\n * the gap between labels, in mm. Each label is followed by exactly this\n * much feed in place of the tear-off feed, so prints stay on the labels.\n */\n gapMm?: number;\n /**\n * Turns each page clockwise by this many degrees before it is sent, for\n * labels designed landscape: the page is drawn the way it reads and prints\n * turned onto the paper. Applied before `offset`, which stays relative to the paper.\n */\n rotate?: Rotation;\n}\n\n/** Quarter turns, in degrees clockwise. */\nexport type Rotation = 0 | 90 | 180 | 270;\n\n/** Different labels in one job, drawn one at a time so a long batch never holds every page at once. */\nexport interface PageSource {\n count: number;\n page: (index: number) => Promise<HTMLCanvasElement>;\n}\n\ntype Encoder = (canvas: HTMLCanvasElement, direction: PrinterProfile['printDirection']) => unknown | Promise<unknown>;\nexport type ClientFactory = (kind: ConnectionKind) => Promise<ClientLike>;\n\nconst DISCONNECTED: PrinterStatus = { state: 'disconnected', profile: null };\n\n/** What the demo printer is called wherever a printer's name shows. */\nexport const DEMO_NAME = 'Demo printer';\n\nconst PRINT_TIMEOUT_MS = 20_000;\nconst PAGE_TIMEOUT_MS = 10_000;\nconst PRINT_END_POLL_MS = 300;\nconst mmToPx = (mm: number, dpi: number): number => (mm / 25.4) * dpi;\n\n/** The page moved by whole pixels on a white canvas of the same size. */\nexport function shiftCanvas(canvas: HTMLCanvasElement, dx: number, dy: number): HTMLCanvasElement {\n dx = Math.round(dx);\n dy = Math.round(dy);\n if (!dx && !dy) return canvas;\n const out = document.createElement('canvas');\n out.width = canvas.width;\n out.height = canvas.height;\n const ctx = out.getContext('2d');\n if (!ctx) return canvas;\n ctx.fillStyle = '#fff';\n ctx.fillRect(0, 0, out.width, out.height);\n ctx.drawImage(canvas, dx, dy);\n return out;\n}\n\n/** The page turned clockwise by `degrees`, a multiple of 90. Returns the canvas itself for no turn. */\nexport function rotateCanvas(canvas: HTMLCanvasElement, degrees: number): HTMLCanvasElement {\n const turn = (((Math.round(degrees / 90) % 4) + 4) % 4) as 0 | 1 | 2 | 3;\n if (!turn) return canvas;\n const out = document.createElement('canvas');\n out.width = turn % 2 ? canvas.height : canvas.width;\n out.height = turn % 2 ? canvas.width : canvas.height;\n const ctx = out.getContext('2d');\n if (!ctx) return canvas;\n ctx.fillStyle = '#fff';\n ctx.fillRect(0, 0, out.width, out.height);\n ctx.translate(out.width / 2, out.height / 2);\n ctx.rotate((turn * Math.PI) / 2);\n ctx.drawImage(canvas, -canvas.width / 2, -canvas.height / 2);\n return out;\n}\n\n/** Crops the page to what the printhead covers. Returns the canvas itself when it already fits. */\nexport function fitToPrinthead(canvas: HTMLCanvasElement, profile: PrinterProfile): HTMLCanvasElement {\n const crop = printheadCrop(canvas.width, canvas.height, profile);\n if (!crop.cropped) return canvas;\n const out = document.createElement('canvas');\n out.width = crop.width;\n out.height = crop.height;\n const ctx = out.getContext('2d');\n if (!ctx) return canvas;\n ctx.drawImage(canvas, crop.x, crop.y, crop.width, crop.height, 0, 0, crop.width, crop.height);\n return out;\n}\n\n/** Black pixels on the page, for the print log. A zero here means the page itself was blank. */\nfunction inkPixels(canvas: HTMLCanvasElement): number | null {\n const ctx = canvas.getContext('2d');\n if (!ctx) return null;\n const { data } = ctx.getImageData(0, 0, canvas.width, canvas.height);\n let n = 0;\n for (let i = 0; i < data.length; i += 4) if (data[i] < 128) n++;\n return n;\n}\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\nfunction withTimeout<T>(p: Promise<T>, ms: number): Promise<T> {\n return new Promise((resolve, reject) => {\n const t = setTimeout(() => reject(new Error('Timeout waiting for the printer to finish')), ms);\n p.then(\n (v) => (clearTimeout(t), resolve(v)),\n (e) => (clearTimeout(t), reject(e)),\n );\n });\n}\n\n/** Translates the library's error codes and messages into short, readable ones. */\nexport function friendlyError(err: unknown): string {\n const msg = err instanceof Error ? err.message : String(err);\n if (/user cancelled|no (device|port) selected|chooser/i.test(msg)) return 'Connection cancelled';\n if (/timeout waiting for the printer to finish/i.test(msg)) return 'The printer did not confirm the print';\n if (/timeout/i.test(msg)) return 'Printer stopped responding';\n if (/lid|cover/i.test(msg)) return 'Close the printer lid';\n if (/paper|label/i.test(msg) && /no|out|missing/i.test(msg)) return 'Load a label roll';\n if (/unknown Bluetooth service|No usable channel/i.test(msg)) return msg;\n if (/disconnected|gatt/i.test(msg)) return 'Printer disconnected';\n return msg || 'Something went wrong';\n}\n\n/**\n * Owns the printer connection. The UI subscribes to status updates and calls\n * connect/print; the raw protocol stays inside niimbluelib.\n */\nexport class PrinterService {\n private client: ClientLike | null = null;\n private status: PrinterStatus = DISCONNECTED;\n private listeners = new Set<(s: PrinterStatus) => void>();\n private manualProfileId: string | null = null;\n private detach: (() => void) | null = null;\n /** Paper moves run one after another, and a print waits for them. */\n private feeding: Promise<void> = Promise.resolve();\n\n /** How the current printer is connected. */\n private kind: ConnectionKind | null = null;\n\n constructor(\n private createClient: ClientFactory,\n private encode: Encoder,\n ) {}\n\n getStatus(): PrinterStatus {\n return this.status;\n }\n\n subscribe(fn: (s: PrinterStatus) => void): () => void {\n this.listeners.add(fn);\n return () => this.listeners.delete(fn);\n }\n\n private set(patch: Partial<PrinterStatus>): void {\n this.replace({ ...this.status, ...patch });\n }\n\n /** Swaps the whole status, so fields from an earlier connection never linger. */\n private replace(status: PrinterStatus): void {\n this.status = status;\n this.listeners.forEach((fn) => fn(this.status));\n }\n\n private matches: Record<string, 'escpos' | 'cat'> = {};\n\n /**\n * Printers matched by their services on earlier connections, by Bluetooth\n * name (from `status.matchedAs` and `status.deviceName`). They connect\n * straight to that client, skipping the NIIMBOT handshake, whose bytes a\n * receipt printer would print as text.\n */\n rememberMatches(matches: Record<string, 'escpos' | 'cat'>): void {\n this.matches = matches;\n }\n\n setManualProfile(id: string | null): void {\n this.manualProfileId = id;\n if (this.client) this.set({ profile: this.currentProfile() });\n }\n\n private currentProfile(): PrinterProfile | null {\n const c = this.client;\n const profile = resolveProfile(c?.getModelMetadata(), c?.getPrintTaskType(), findManualProfile(this.manualProfileId), c?.getPrinterInfo().protocolVersion);\n // Whatever printer stands in for the demo, it never passes for a real model.\n return profile && this.kind === 'demo' ? { ...profile, name: DEMO_NAME } : profile;\n }\n\n private readRfid(): PrinterStatus['label'] {\n // Printers that cannot sense the roll keep the size the user sets, under a key of their own.\n const manual = this.client?.rollKey?.();\n if (manual) return { barcode: manual, manual: true };\n const info = this.client?.getRfidInfo();\n if (!info) return undefined;\n const paper = info.paperInfo?.valid ? info.paperInfo : undefined;\n const tag = info.labelRfidInfo?.tagPresent ? info.labelRfidInfo : undefined;\n if (!paper && !tag) return undefined;\n return {\n widthMm: paper?.paperWidth,\n heightMm: paper?.paperHeight,\n remaining: tag && tag.allPaper !== undefined && tag.allPaper >= 0 ? Math.max(0, tag.allPaper - (tag.usedPaper ?? 0)) : undefined,\n capacity: tag && tag.allPaper !== undefined && tag.allPaper > 0 ? tag.allPaper : undefined,\n barcode: tag?.barCode || undefined,\n paper: paperFromLabelType(tag?.consumablesType ?? paper?.paperType),\n };\n }\n\n async connect(kind: ConnectionKind): Promise<void> {\n if (this.client) await this.disconnect();\n this.kind = kind;\n this.replace({ ...DISCONNECTED, state: 'connecting' });\n let client: ClientLike;\n try {\n client = await this.createClient(kind);\n client.unrecognisedAs?.(this.manualProfileId);\n client.rememberMatches?.(this.matches);\n const info = await client.connect();\n this.client = client;\n this.set({ deviceName: info.deviceName });\n } catch (err) {\n this.client = null;\n this.replace(DISCONNECTED);\n throw new Error(friendlyError(err));\n }\n\n const onDisconnect = () => this.handleDisconnect();\n const onHeartbeat = (e: { data: { batteryPercents?: number; lidClosed?: boolean; paperInserted?: boolean } }) =>\n this.set({ battery: e.data.batteryPercents ?? this.status.battery, lidClosed: e.data.lidClosed, paperInserted: e.data.paperInserted });\n const onRfid = () => this.set({ label: this.readRfid() });\n const onProgress = (e: { page: number; pagesTotal: number; sending?: number }) =>\n this.set({ progress: { page: e.page, total: e.pagesTotal, ...(e.sending !== undefined ? { sending: e.sending } : {}) } });\n client.on('disconnect', onDisconnect);\n client.on('heartbeat', onHeartbeat);\n client.on('rfidinfofetched', onRfid);\n client.on('printprogress', onProgress);\n this.detach = () => {\n client.off('disconnect', onDisconnect);\n client.off('heartbeat', onHeartbeat);\n client.off('rfidinfofetched', onRfid);\n client.off('printprogress', onProgress);\n };\n\n const meta = client.getModelMetadata();\n this.set({\n state: 'ready',\n battery: client.getPrinterInfo().batteryPercents,\n detectedModel: meta?.model,\n profile: this.currentProfile(),\n label: this.readRfid(),\n ...(client.feedPaper ? { canFeed: true } : {}),\n ...(client.feedPaper && client.feedsBack ? { canRetract: true } : {}),\n ...(client.headUnknown && client.setHeadDots ? { headUnknown: true } : {}),\n ...(client.matchedAs ? { matchedAs: client.matchedAs } : {}),\n });\n client.startHeartbeat();\n }\n\n private handleDisconnect(): void {\n this.detach?.();\n this.detach = null;\n this.client = null;\n this.replace(DISCONNECTED);\n }\n\n async disconnect(): Promise<void> {\n const c = this.client;\n if (!c) return;\n c.stopHeartbeat();\n try {\n await c.disconnect();\n } finally {\n this.handleDisconnect();\n }\n }\n\n /**\n * B21_V1 finishes by polling \"print end\", but niimbluelib's poll cancels its\n * own timer on the first reply, so it never resolves unless the printer is\n * already done. That task polls here instead.\n */\n private async waitForFinished(client: ClientLike, task: PrintTaskLike, name: string, copies: number): Promise<void> {\n if (name !== 'B21_V1') return task.waitForFinished();\n this.set({ progress: { page: 0, total: copies } });\n while (!(await client.protocol.printEnd())) await sleep(PRINT_END_POLL_MS);\n this.set({ progress: { page: copies, total: copies } });\n }\n\n /**\n * Sets the print width, in mm, on a printer whose head width is not known\n * (`status.headUnknown`); the profile follows. Other printers keep theirs.\n */\n setPrintWidth(mm: number): void {\n const client = this.client;\n const profile = this.status.profile;\n if (!client?.setHeadDots || !this.status.headUnknown || !profile) return;\n client.setHeadDots(mmToPx(mm, profile.dpi));\n const next = this.currentProfile();\n if (next?.printheadPixels !== profile.printheadPixels) this.set({ profile: next });\n }\n\n /**\n * Moves the paper by `mm`, out of the printer when positive and back in when\n * negative, to line it up on printers that cannot sense it.\n */\n feed(mm: number): Promise<void> {\n const client = this.client;\n const profile = this.status.profile;\n if (!client || !profile) return Promise.reject(new Error('Connect a printer first'));\n if (!client.feedPaper) return Promise.reject(new Error('This printer finds the paper itself'));\n if (mm < 0 && !client.feedsBack) return Promise.reject(new Error('This printer cannot move the paper back'));\n if (this.status.state === 'printing') return Promise.reject(new Error('Wait for the print to finish'));\n const dots = Math.round(mmToPx(mm, profile.dpi));\n const run = this.feeding.then(() => (dots ? client.feedPaper!(dots) : undefined));\n this.feeding = run.catch(() => {});\n return run.catch((err) => {\n throw new Error(friendlyError(err));\n });\n }\n\n async print(source: HTMLCanvasElement | PageSource, opts: PrintOptions): Promise<void> {\n const client = this.client;\n const profile = this.status.profile;\n if (!client) throw new Error('Connect a printer first');\n if (!profile) throw new Error('Choose your printer type');\n if (this.status.state === 'printing') throw new Error('Already printing');\n await this.feeding;\n\n const pages: PageSource = 'page' in source ? source : { count: 1, page: async () => source };\n const copies = Math.max(1, Math.floor(opts.copies));\n const total = copies * Math.max(1, pages.count);\n const density = Math.min(profile.densityMax, Math.max(profile.densityMin, Math.round(opts.density)));\n const task = client.protocol.newPrintTask(profile.printTask, {\n totalPages: total,\n density,\n labelType: opts.labelType ?? 1,\n ...(opts.gapMm !== undefined && client.feedPaper ? { gapDots: Math.round(mmToPx(Math.max(0, opts.gapMm), profile.dpi)) } : {}),\n pageColor: 0,\n statusPollIntervalMs: 100,\n statusTimeoutMs: 8000,\n });\n\n // Heartbeat queries interleaved with page data spoil prints on some models.\n client.stopHeartbeat();\n this.set({ state: 'printing', progress: { page: 0, total } });\n try {\n const offset = opts.offset ?? { x: 0, y: 0 };\n for (let i = 0; i < pages.count; i++) {\n const canvas = rotateCanvas(await pages.page(i), opts.rotate ?? 0);\n const shifted = shiftCanvas(canvas, mmToPx(offset.x, profile.dpi), mmToPx(offset.y, profile.dpi));\n const page = fitToPrinthead(shifted, profile);\n const image = client.encodePage ? await client.encodePage(page) : await this.encode(page, profile.printDirection);\n if (i === 0) {\n console.info('[niimai] print', {\n profile: profile.id,\n task: profile.printTask,\n direction: profile.printDirection,\n printhead: profile.printheadPixels,\n page: `${canvas.width}x${canvas.height}`,\n rotate: opts.rotate ?? 0,\n sent: `${page.width}x${page.height}`,\n density,\n copies,\n pages: pages.count,\n ink: inkPixels(page),\n });\n await task.printInit();\n }\n await task.printPage(image, copies);\n }\n await withTimeout(this.waitForFinished(client, task, profile.printTask, total), PRINT_TIMEOUT_MS + total * PAGE_TIMEOUT_MS);\n } catch (err) {\n throw new Error(friendlyError(err));\n } finally {\n try {\n await task.printEnd();\n } catch {\n // The page already failed; the original error is the useful one.\n }\n if (this.client) {\n client.startHeartbeat();\n this.set({ state: 'ready', progress: undefined });\n void client.fetchRfidInfo().then(() => this.set({ label: this.readRfid() }), () => {});\n }\n }\n }\n}\n","import type { PrinterModelMeta, PrintTaskName } from '@mmote/niimbluelib';\n\n/**\n * The slice of niimbluelib's client the app uses. Keeping it narrow lets the\n * mock printer and the tests stand in for real hardware.\n */\nexport interface PrintTaskLike {\n printInit(): Promise<void>;\n printPage(image: unknown, quantity?: number): Promise<void>;\n waitForFinished(): Promise<void>;\n printEnd(): Promise<boolean>;\n}\n\nexport interface ClientLike {\n connect(): Promise<{ deviceName?: string }>;\n disconnect(): Promise<void>;\n isConnected(): boolean;\n on(event: string, fn: (e: any) => void): unknown;\n off(event: string, fn: (e: any) => void): unknown;\n getPrinterInfo(): { batteryPercents?: number; modelId?: number; printheadWidth?: number; protocolVersion?: number };\n getModelMetadata(): PrinterModelMeta | undefined;\n getPrintTaskType(): PrintTaskName | undefined;\n getRfidInfo(): {\n labelRfidInfo?: { tagPresent?: boolean; barCode?: string; allPaper?: number; usedPaper?: number; consumablesType?: number };\n paperInfo?: { valid?: boolean; paperWidth?: number; paperHeight?: number; paperType?: number };\n };\n fetchRfidInfo(): Promise<unknown>;\n startHeartbeat(): void;\n stopHeartbeat(): void;\n isHeartbeatStarted?(): boolean;\n protocol: {\n newPrintTask(name: PrintTaskName, options: Record<string, unknown>): PrintTaskLike;\n /** True once the printer has finished every page. */\n printEnd(): Promise<boolean>;\n };\n /** Clients that encode pages themselves (cat printers); NIIMBOT pages go through niimbluelib's encoder. */\n encodePage?(canvas: HTMLCanvasElement): unknown;\n /**\n * Label printers that cannot sense the roll: a key to remember the roll\n * size the user sets, per printer. NIIMBOT printers read their rolls instead.\n */\n rollKey?(): string;\n /** Printers that cannot sense the paper: moves it by `dots`, back into the printer when negative. */\n feedPaper?(dots: number): Promise<void>;\n /** feedPaper can also move the paper back. */\n feedsBack?: boolean;\n /** The head's width is not known (a stock module name, or a printer matched by its services): `setHeadDots` sets it. */\n headUnknown?: boolean;\n /** Sets how many dots the head prints across, on printers whose head width is not known. */\n setHeadDots?(dots: number): void;\n /**\n * Bluetooth: the kind of printer to treat one with an unknown name as, set\n * before connecting: an id from PRINTER_TYPES, or a NIIMBOT profile id the\n * client ignores.\n */\n unrecognisedAs?(typeId: string | null): void;\n /** Bluetooth: printers matched by their services on earlier connections, by name; they skip the NIIMBOT handshake. */\n rememberMatches?(matches: Record<string, 'escpos' | 'cat'>): void;\n /** Bluetooth: what the printer just connected was matched as by its services, if it was. */\n matchedAs?: 'escpos' | 'cat';\n}\n\n/** `serial`: a printer on a USB cable that shows up as a serial port, as NIIMBOT printers do. `usb`: one that shows up as a standard USB printer. */\nexport type ConnectionKind = 'bluetooth' | 'serial' | 'usb' | 'demo';\n\nexport const supportsBluetooth = (): boolean => typeof navigator !== 'undefined' && 'bluetooth' in navigator;\nexport const supportsSerial = (): boolean => typeof navigator !== 'undefined' && 'serial' in navigator;\nexport const supportsUsb = (): boolean => typeof navigator !== 'undefined' && 'usb' in navigator;\n\nconst SUPPORTS: Record<Exclude<ConnectionKind, 'demo'>, () => boolean> = { bluetooth: supportsBluetooth, serial: supportsSerial, usb: supportsUsb };\n\nexport type Platform = 'ios' | 'android' | 'desktop';\n\nexport function detectPlatform(ua = typeof navigator === 'undefined' ? '' : navigator.userAgent, touchPoints = typeof navigator === 'undefined' ? 0 : navigator.maxTouchPoints): Platform {\n // iPadOS reports itself as a Mac; touch support gives it away.\n if (/iPhone|iPad|iPod/.test(ua) || (/Macintosh/.test(ua) && touchPoints > 1)) return 'ios';\n if (/Android/.test(ua)) return 'android';\n return 'desktop';\n}\n\nexport type OperatingSystem = 'ios' | 'android' | 'chromeos' | 'linux' | 'mac' | 'windows' | 'other';\nexport type BrowserName = 'chrome' | 'edge' | 'opera' | 'brave' | 'samsung' | 'bluefy' | 'firefox' | 'safari' | 'other';\n\nexport interface BrowserInfo {\n os: OperatingSystem;\n browser: BrowserName;\n /** Built on Chromium, so Web Bluetooth, Web Serial and WebUSB can be there. */\n chromium: boolean;\n /** Served over https or from localhost; browsers hide both APIs elsewhere. */\n secure: boolean;\n}\n\n/** What the browser tells about itself. Defaults to the running browser. */\nexport interface BrowserEnv {\n ua: string;\n touchPoints: number;\n /** Brave hides itself in the user agent; it has `navigator.brave` instead. */\n brave: boolean;\n secure: boolean;\n}\n\nfunction currentEnv(): BrowserEnv {\n if (typeof navigator === 'undefined') return { ua: '', touchPoints: 0, brave: false, secure: true };\n return {\n ua: navigator.userAgent,\n touchPoints: navigator.maxTouchPoints,\n brave: 'brave' in navigator,\n secure: typeof window === 'undefined' || window.isSecureContext !== false,\n };\n}\n\nexport function detectBrowser(env: Partial<BrowserEnv> = {}): BrowserInfo {\n const { ua, touchPoints, brave, secure } = { ...currentEnv(), ...env };\n const platform = detectPlatform(ua, touchPoints);\n const os: OperatingSystem =\n platform !== 'desktop' ? platform : /CrOS/.test(ua) ? 'chromeos' : /Windows/.test(ua) ? 'windows' : /Macintosh|Mac OS X/.test(ua) ? 'mac' : /Linux|X11|BSD/.test(ua) ? 'linux' : 'other';\n const browser: BrowserName = brave\n ? 'brave'\n : /Bluefy/i.test(ua)\n ? 'bluefy'\n : /Firefox|FxiOS/.test(ua)\n ? 'firefox'\n : /Edg(e|A|iOS)?\\//.test(ua)\n ? 'edge'\n : /OPR\\/|Opera/.test(ua)\n ? 'opera'\n : /SamsungBrowser/.test(ua)\n ? 'samsung'\n : /Chrome\\/|CriOS|Chromium/.test(ua)\n ? 'chrome'\n : /Safari\\//.test(ua)\n ? 'safari'\n : 'other';\n // Every iOS browser runs on WebKit, whatever its name.\n const chromium = os !== 'ios' && /Chrome\\/|Chromium/.test(ua);\n return { os, browser, chromium, secure };\n}\n\n/**\n * A Help topic that shows how to get a connection type working:\n * - `linux`: Chrome and other Chromium browsers on Linux need a flag for Bluetooth.\n * - `brave`: Brave keeps Bluetooth and USB behind flags.\n * - `ios`: iPhone and iPad browsers, and Bluefy.\n * - `https`: the page has to be served over https.\n * - `browsers`: which browsers work.\n * - `usb`: Windows and Linux hold on to USB printers until told to let the browser have them.\n */\nexport type SetupTopic = 'linux' | 'brave' | 'ios' | 'https' | 'browsers' | 'usb';\n\nexport interface SupportIssue {\n /** In a few words, what is missing or what to do. */\n reason: string;\n topic: SetupTopic;\n}\n\nconst BROWSER_NAMES: Partial<Record<BrowserName, string>> = { firefox: 'Firefox', safari: 'Safari', samsung: 'Samsung Internet', opera: 'Opera' };\n\n/**\n * Why a connection type cannot be used in this browser, and where to read how\n * to fix it, or null when it can be used.\n */\nexport function supportIssue(kind: ConnectionKind, env: Partial<BrowserEnv> = {}): SupportIssue | null {\n if (kind === 'demo') return null;\n if (SUPPORTS[kind]()) return null;\n const { os, browser, chromium, secure } = detectBrowser(env);\n const bluetooth = kind === 'bluetooth';\n if (os === 'ios') return { reason: bluetooth ? 'On iPhone and iPad, use the Bluefy browser' : 'Not available on iPhone and iPad', topic: 'ios' };\n if (!secure && chromium) return { reason: 'Needs an https page', topic: 'https' };\n if (browser === 'brave') return { reason: 'Turn it on in Brave flags', topic: 'brave' };\n if (os === 'android') {\n if (!bluetooth) return { reason: 'Needs Chrome or Edge on a computer', topic: 'browsers' };\n return { reason: browser === 'chrome' ? 'Update Chrome' : 'Open niimai in Chrome', topic: 'browsers' };\n }\n if (bluetooth && os === 'linux' && chromium) return { reason: 'Needs a Chrome flag on Linux', topic: 'linux' };\n const name = BROWSER_NAMES[browser];\n if (name) return { reason: `${name} has no ${bluetooth ? 'Bluetooth' : 'USB'} access; use Chrome or Edge`, topic: 'browsers' };\n if (chromium) return { reason: 'Turned off in this browser', topic: 'browsers' };\n return { reason: 'Needs Chrome or Edge', topic: 'browsers' };\n}\n\n/** Stand-ins for the platform-only signature below. */\nconst PLATFORM_ENVS: Record<Platform, Partial<BrowserEnv>> = {\n ios: { ua: 'iPhone', touchPoints: 5, brave: false },\n android: { ua: 'Android Firefox', touchPoints: 5, brave: false },\n desktop: { ua: '', touchPoints: 0, brave: false },\n};\n\n/** supportIssue's reason alone. */\nexport function unsupportedReason(kind: ConnectionKind, platform?: Platform): string | null {\n return supportIssue(kind, platform ? PLATFORM_ENVS[platform] : {})?.reason ?? null;\n}\n\n/**\n * Errors browsers raise when Bluetooth or USB is there but cannot be used:\n * the adapter is off or missing, the browser turned the API off, or the\n * system keeps a USB printer for itself. The Help topic to point to, or null.\n */\nexport function connectErrorTopic(err: unknown): SetupTopic | null {\n const message = err instanceof Error ? err.message : String(err);\n if (/globally disabled/i.test(message)) return 'brave';\n if (/claim interface|Access denied|Unable to open device/i.test(message)) return 'usb';\n if (/adapter not available|Bluetooth adapter|Bluetooth is (turned off|disabled|not available)/i.test(message)) return detectBrowser().os === 'linux' ? 'linux' : 'browsers';\n return null;\n}\n","/**\n * Label and receipt printers on a USB cable that show up as standard USB\n * printers (interface class 7), which Web Serial cannot see. They take the\n * same TSPL or ESC/POS bytes as over Bluetooth, through WebUSB. NIIMBOT\n * printers show up as serial ports and keep using Web Serial.\n */\nimport { cableClient, findPrinterType, identifyUsbPrinter, type CatalogEntry, type PrinterFamily, type PrinterType } from './catalog';\nimport { supportsUsb } from './client';\nimport type { ThermalTransport } from './thermal';\nimport { RoutedClient } from './routed';\n\nexport const USB_PRINTER_CLASS = 7;\n\n/** USB moves data far faster than Bluetooth and paces itself, so writes are large and back to back. */\nconst USB_PACE = { chunkMs: 0, pauseMs: 100 };\nconst USB_CHUNK = 4096;\n/** Label printers may not send \"OK\" over USB; a status query after this long tells a fault from a quiet printer. */\nconst USB_ACK_TIMEOUT_MS = 5_000;\n\n/**\n * The fields of an IEEE 1284 device ID, keys in capitals:\n * \"MFG:ACME;COMMAND SET:TSC/ESC/POS;MODEL:CC390;\" → { MFG, 'COMMAND SET', MODEL }.\n */\nexport function parseDeviceId(text: string): Record<string, string> {\n const fields: Record<string, string> = {};\n for (const part of text.split(';')) {\n const at = part.indexOf(':');\n if (at > 0) fields[part.slice(0, at).trim().toUpperCase()] = part.slice(at + 1).trim();\n }\n return fields;\n}\n\n/** The printer languages a device ID names, TSPL first: label printers often take ESC/POS as well. */\nexport function familiesFromDeviceId(text: string): PrinterFamily[] {\n const id = parseDeviceId(text);\n const commands = id.CMD ?? id['COMMAND SET'] ?? '';\n const families: PrinterFamily[] = [];\n if (/\\b(TSPL2?|TSC)\\b/i.test(commands)) families.push('tspl');\n if (/ESC\\/?POS|\\bEPSON\\b/i.test(commands)) families.push('escpos');\n return families;\n}\n\nconst isPrinter = (device: USBDevice) =>\n device.configuration?.interfaces.some((i) => i.alternates.some((a) => a.interfaceClass === USB_PRINTER_CLASS)) ??\n // Devices not yet opened show no configuration; the chooser only lists printers anyway.\n true;\n\n/** USB printers this site may already use that are plugged in. */\nexport async function rememberedUsbPrinters(): Promise<USBDevice[]> {\n if (!supportsUsb()) return [];\n return (await navigator.usb.getDevices().catch(() => [])).filter(isPrinter);\n}\n\n/** Opens a USB printer and claims its printer interface. Also reads the printer's device ID. */\nexport async function usbTransport(device: USBDevice): Promise<{ transport: ThermalTransport; deviceId: string }> {\n await device.open();\n if (!device.configuration) await device.selectConfiguration(1);\n const iface = device.configuration!.interfaces.find((i) => i.alternates.some((a) => a.interfaceClass === USB_PRINTER_CLASS));\n if (!iface) throw new Error(`${device.productName ?? 'This device'} is not a USB printer`);\n const alt = iface.alternates.find((a) => a.interfaceClass === USB_PRINTER_CLASS)!;\n await device.claimInterface(iface.interfaceNumber);\n if (alt.alternateSetting !== 0) await device.selectAlternateInterface(iface.interfaceNumber, alt.alternateSetting);\n const out = alt.endpoints.find((e) => e.direction === 'out' && e.type === 'bulk');\n const back = alt.endpoints.find((e) => e.direction === 'in' && e.type === 'bulk');\n if (!out) throw new Error(`${device.productName ?? 'This printer'} has no way to send it data`);\n\n // GET_DEVICE_ID: interface in the high byte of the index, alternate setting in the low byte.\n const deviceId = await device\n .controlTransferIn({ requestType: 'class', recipient: 'interface', request: 0, value: 0, index: (iface.interfaceNumber << 8) | alt.alternateSetting }, 1024)\n .then((r) => (r.data ? new TextDecoder().decode(new Uint8Array(r.data.buffer, r.data.byteOffset + 2, Math.max(0, r.data.byteLength - 2))) : ''))\n .catch(() => '');\n\n const notify: ((d: Uint8Array) => void)[] = [];\n const gone: (() => void)[] = [];\n let open = true;\n // Replies (TSPL's \"OK\", status bytes) arrive on the bulk in endpoint; read it until the device closes.\n if (back) {\n void (async () => {\n while (open) {\n const r = await device.transferIn(back.endpointNumber, back.packetSize).catch(() => null);\n if (!r) break;\n if (r.data?.byteLength) notify.forEach((fn) => fn(new Uint8Array(r.data!.buffer.slice(r.data!.byteOffset, r.data!.byteOffset + r.data!.byteLength))));\n }\n })();\n }\n const onUnplug = (e: USBConnectionEvent) => {\n if (e.device !== device) return;\n open = false;\n navigator.usb.removeEventListener('disconnect', onUnplug);\n gone.forEach((fn) => fn());\n };\n navigator.usb.addEventListener('disconnect', onUnplug);\n\n return {\n deviceId,\n transport: {\n name: device.productName || undefined,\n chunkBytes: USB_CHUNK,\n write: async (chunk) => {\n const r = await device.transferOut(out.endpointNumber, chunk.slice());\n if (r.status !== 'ok') throw new Error('The printer did not take the data');\n },\n onNotify: (fn) => notify.push(fn),\n onDisconnect: (fn) => gone.push(fn),\n disconnect: async () => {\n open = false;\n navigator.usb.removeEventListener('disconnect', onUnplug);\n await device.releaseInterface(iface.interfaceNumber).catch(() => {});\n await device.close().catch(() => {});\n },\n },\n };\n}\n\nconst PRINTER_TYPE_NAMES: Record<PrinterFamily, string> = { escpos: 'receipt printer', tspl: 'label printer', cat: 'cat printer' };\n\n/**\n * The USB printer behind the USB button for label and receipt printers. A\n * printer this site already uses connects straight away when it is the only\n * one plugged in; otherwise the browser lists USB printers. The printer's\n * language comes from the catalog, then from the type picked under Advanced\n * when the printer speaks it (or does not say), then from the device ID the\n * printer reports.\n */\nexport class UsbPrinter extends RoutedClient {\n private fallback: PrinterType | null = null;\n\n unrecognisedAs(typeId: string | null) {\n this.fallback = findPrinterType(typeId);\n }\n\n async connect() {\n const remembered = await rememberedUsbPrinters();\n const device = remembered.length === 1 ? remembered[0] : await navigator.usb.requestDevice({ filters: [{ classCode: USB_PRINTER_CLASS }] });\n const { transport, deviceId } = await usbTransport(device);\n const name = device.productName || 'This printer';\n const families = familiesFromDeviceId(deviceId);\n const picked = this.fallback && (!families.length || families.includes(this.fallback.family)) ? this.fallback : null;\n const entry: CatalogEntry | null =\n identifyUsbPrinter(device) ?? picked ?? (families.length ? { family: families[0], model: { prefix: '', confidence: 'medium', generic: true } } : null);\n if (!entry) {\n await transport.disconnect();\n throw new Error(`${name} did not say which printer language it speaks. Pick its type under Advanced and connect again.`);\n }\n this.inner = cableClient(entry, transport, USB_PACE, USB_ACK_TIMEOUT_MS);\n if (!this.inner) {\n await transport.disconnect();\n throw new Error(`A ${PRINTER_TYPE_NAMES[entry.family]} cannot print over USB`);\n }\n return this.inner.connect();\n }\n}\n","/**\n * libniimai: connect to NIIMBOT, TSPL, ESC/POS and cat printers from the\n * browser over Web Bluetooth, Web Serial or WebUSB, and print a canvas on them.\n */\n/// <reference types=\"web-bluetooth\" preserve=\"true\" />\n/// <reference types=\"w3c-web-serial\" preserve=\"true\" />\n/// <reference types=\"w3c-web-usb\" preserve=\"true\" />\nimport { BluetoothPrinter } from './bluetooth';\nimport type { ClientLike, ConnectionKind } from './client';\nimport { MockClient } from './mock';\nimport { PrinterService, type ClientFactory } from './service';\nimport { UsbPrinter } from './usb';\n\n// niimbluelib loads only when it is needed, so pages that never meet a NIIMBOT stay light.\nconst niimbot = () => import('@mmote/niimbluelib');\n\n/**\n * A client for one kind of connection. Bluetooth finds the printer family\n * from the device the user picks; serial is NIIMBOT only; usb is for label\n * and receipt printers that show up as standard USB printers; demo is a\n * printer in memory.\n */\nexport async function createClient(kind: ConnectionKind): Promise<ClientLike> {\n if (kind === 'demo') return new MockClient();\n if (kind === 'bluetooth') return new BluetoothPrinter(niimbot);\n if (kind === 'usb') return new UsbPrinter();\n const lib = await niimbot();\n return new lib.NiimbotSerialClient() as unknown as ClientLike;\n}\n\nexport interface CreatePrinterOptions {\n /**\n * Makes the client for each connection. Defaults to createClient; wrap it\n * to change one kind of connection and keep the others.\n */\n createClient?: ClientFactory;\n}\n\n/** A ready printer service: connect, watch its status, print canvases. */\nexport function createPrinter(options: CreatePrinterOptions = {}): PrinterService {\n return new PrinterService(options.createClient ?? createClient, async (canvas, direction) =>\n (await niimbot()).ImageEncoder.encodeCanvas(canvas, 0, direction),\n );\n}\n\nexport { PrinterService, DEMO_NAME, friendlyError, fitToPrinthead, rotateCanvas, shiftCanvas } from './service';\nexport type { ClientFactory, PageSource, PrinterState, PrinterStatus, PrintOffset, PrintOptions, Rotation } from './service';\n\nexport { connectErrorTopic, detectBrowser, detectPlatform, supportIssue, supportsBluetooth, supportsSerial, supportsUsb, unsupportedReason } from './client';\nexport type { BrowserEnv, BrowserInfo, BrowserName, ClientLike, ConnectionKind, OperatingSystem, Platform, PrintTaskLike, SetupTopic, SupportIssue } from './client';\n\nexport { correctTask, findManualProfile, inkOutsideHead, MANUAL_PROFILES, printheadCrop, printheadMm, resolveProfile } from './profiles';\nexport type { LabelSize, PrintDirection, PrinterProfile } from './profiles';\n\nexport { effectivePaper, labelTypeOf, PAPER_TYPES, paperFromLabelType, paperName } from './paper';\nexport type { PaperType } from './paper';\n\nexport { findPrinterType, identifyPrinter, identifyUsbPrinter, PRINTER_CATALOG, PRINTER_TYPES } from './catalog';\nexport type { CatalogEntry, PrinterFamily, PrinterType } from './catalog';\n\nexport { BluetoothPrinter, byServices, type MatchedFamily, type MatchedPrinters } from './bluetooth';\nexport { familiesFromDeviceId, parseDeviceId, rememberedUsbPrinters, UsbPrinter, usbTransport, USB_PRINTER_CLASS } from './usb';\nexport { describeBleError } from './ble';\nexport { DEMO_META, MockClient } from './mock';\n\nexport { modelFor, THERMAL_DEFAULT_SIZE, THERMAL_HEAD_DOTS, THERMAL_HEAD_MM, THERMAL_SIZES, ThermalPrinterClient, webTransport } from './thermal';\nexport type { Confidence, Pace, ThermalModel, ThermalTransport } from './thermal';\n\nexport { CAT_MODELS, CAT_UNSUPPORTED, CatPrinterClient, catFeed, catModelFor, printJob as catJob } from './cat';\nexport type { CatModel } from './cat';\nexport { ESCPOS_MODELS, EscPosPrinterClient, escposFeed, escposJob, escposModelFor } from './escpos';\nexport { TSPL_MODELS, TSPL_SIZES, TsplPrinterClient, tsplJob, tsplModelFor } from './tspl';\n"],"mappings":"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"}
|
package/dist/routed.d.ts
CHANGED
|
@@ -48,5 +48,7 @@ export declare abstract class RoutedClient implements ClientLike {
|
|
|
48
48
|
get encodePage(): ((canvas: HTMLCanvasElement) => unknown) | undefined;
|
|
49
49
|
get rollKey(): (() => string) | undefined;
|
|
50
50
|
get feedPaper(): ((dots: number) => Promise<void>) | undefined;
|
|
51
|
+
get headUnknown(): boolean | undefined;
|
|
52
|
+
get setHeadDots(): ((dots: number) => void) | undefined;
|
|
51
53
|
get feedsBack(): boolean | undefined;
|
|
52
54
|
}
|
package/dist/service.d.ts
CHANGED
|
@@ -34,6 +34,10 @@ export interface PrinterStatus {
|
|
|
34
34
|
canFeed?: boolean;
|
|
35
35
|
/** `feed()` can also move the paper back. */
|
|
36
36
|
canRetract?: boolean;
|
|
37
|
+
/** The head's width is a guess: `setPrintWidth()` sets it, from the paper loaded. */
|
|
38
|
+
headUnknown?: boolean;
|
|
39
|
+
/** The printer was matched by its services, as this family: keep it with `rememberMatches()` so it skips the NIIMBOT handshake next time. */
|
|
40
|
+
matchedAs?: 'escpos' | 'cat';
|
|
37
41
|
}
|
|
38
42
|
/** Nudges the printed image, in mm: positive x moves right, positive y moves down. */
|
|
39
43
|
export interface PrintOffset {
|
|
@@ -100,6 +104,14 @@ export declare class PrinterService {
|
|
|
100
104
|
private set;
|
|
101
105
|
/** Swaps the whole status, so fields from an earlier connection never linger. */
|
|
102
106
|
private replace;
|
|
107
|
+
private matches;
|
|
108
|
+
/**
|
|
109
|
+
* Printers matched by their services on earlier connections, by Bluetooth
|
|
110
|
+
* name (from `status.matchedAs` and `status.deviceName`). They connect
|
|
111
|
+
* straight to that client, skipping the NIIMBOT handshake, whose bytes a
|
|
112
|
+
* receipt printer would print as text.
|
|
113
|
+
*/
|
|
114
|
+
rememberMatches(matches: Record<string, 'escpos' | 'cat'>): void;
|
|
103
115
|
setManualProfile(id: string | null): void;
|
|
104
116
|
private currentProfile;
|
|
105
117
|
private readRfid;
|
|
@@ -112,6 +124,11 @@ export declare class PrinterService {
|
|
|
112
124
|
* already done. That task polls here instead.
|
|
113
125
|
*/
|
|
114
126
|
private waitForFinished;
|
|
127
|
+
/**
|
|
128
|
+
* Sets the print width, in mm, on a printer whose head width is not known
|
|
129
|
+
* (`status.headUnknown`); the profile follows. Other printers keep theirs.
|
|
130
|
+
*/
|
|
131
|
+
setPrintWidth(mm: number): void;
|
|
115
132
|
/**
|
|
116
133
|
* Moves the paper by `mm`, out of the printer when positive and back in when
|
|
117
134
|
* negative, to line it up on printers that cannot sense it.
|
package/dist/thermal.d.ts
CHANGED
|
@@ -56,7 +56,10 @@ export interface Pace {
|
|
|
56
56
|
chunkMs: number;
|
|
57
57
|
/** How often to look again while the printer asks to wait. */
|
|
58
58
|
pauseMs: number;
|
|
59
|
+
/** Pause after a reset command that starts a job: some printers drop what arrives while they reset. */
|
|
60
|
+
resetMs?: number;
|
|
59
61
|
}
|
|
62
|
+
export declare const sleep: (ms: number) => Promise<unknown>;
|
|
60
63
|
/**
|
|
61
64
|
* A thermal printer behind the same interface as a NIIMBOT client, so the
|
|
62
65
|
* printer service drives both. It reports itself through model metadata with
|