libniimai 0.5.0 → 0.7.0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # libniimai
2
2
 
3
- Print from the browser on four families of thermal printers: **NIIMBOT**, **TSPL label printers**, **ESC/POS receipt printers** and **cat printers**. One API connects, detects the printer and prints any canvas, over Web Bluetooth or Web Serial. No server, no drivers, no app.
3
+ Print from the browser on four families of thermal printers: **NIIMBOT**, **TSPL label printers**, **ESC/POS receipt printers** and **cat printers**. One API connects, detects the printer and prints any canvas, over Web Bluetooth, Web Serial or WebUSB. No server, no drivers, no app.
4
4
 
5
5
  libniimai powers [niimai](https://niimai.app), the label designer.
6
6
 
@@ -9,11 +9,11 @@ libniimai powers [niimai](https://niimai.app), the label designer.
9
9
  | Family | Paper | Print width | Connection | Models |
10
10
  |---|---|---|---|---|
11
11
  | **NIIMBOT** | Labels with gaps, black mark, continuous, transparent | 12 mm and up, by model | Bluetooth, USB | D11, D110, D101, B1, B18, B21, B3S and more |
12
- | **TSPL label printers** | Labels with gaps, black mark, continuous | Up to 80 mm | Bluetooth | QR380A (sold as CC390), Gprinter GP-M322 |
13
- | **ESC/POS receipt printers** | 58 or 80 mm, continuous or stickers with gaps | 48 or 72 mm | Bluetooth | MP583, MPT-II, Xprinter XP-58IIL, PT-210, RPP02, MPT-3, RPP300 and more |
12
+ | **TSPL label printers** | Labels with gaps, black mark, continuous | Up to 80 mm | Bluetooth, USB | QR380A (sold as CC390), Gprinter GP-M322 |
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 is treated as a NIIMBOT, unless a type from `PRINTER_TYPES` is set for unrecognised printers. Every family gets the same treatment:
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.
@@ -90,12 +90,14 @@ Returns a `PrinterService`, which owns one printer connection.
90
90
 
91
91
  | Member | What it does |
92
92
  | --- | --- |
93
- | `connect(kind)` | Opens the browser's device picker and connects. `kind` is `'bluetooth'` (every family), `'serial'` (NIIMBOT over USB) or `'demo'` (a printer in memory, for trying things out). |
93
+ | `connect(kind)` | Opens the browser's device picker and connects. `kind` is `'bluetooth'` (every family), `'serial'` (NIIMBOT over USB, as a serial port), `'usb'` (label and receipt printers that show up as standard USB printers) or `'demo'` (a printer in memory, for trying things out). |
94
94
  | `disconnect()` | Closes the connection. |
95
95
  | `print(source, options)` | Prints a canvas, or a `{ count, page(i) }` source for a batch of different labels. |
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
 
@@ -159,14 +163,15 @@ Every piece is exported, for building your own flow:
159
163
  - `createClient(kind)` returns a raw client for one connection.
160
164
  - `TsplPrinterClient`, `EscPosPrinterClient` and `CatPrinterClient` take any `ThermalTransport`, so they also work over other links.
161
165
  - `tsplJob`, `escposJob` and `catJob` build the bytes for one page; `escposFeed` and `catFeed` the bytes that move the paper.
162
- - `identifyPrinter(name)` matches a Bluetooth name to a known model across every family, from `PRINTER_CATALOG`; `tsplModelFor`, `escposModelFor` and `catModelFor` look in one family.
163
- - `supportIssue(kind)` says why a connection type cannot be used in the current browser, as `{ reason, topic }`, or null when it can. `topic` names what fixes it: `linux` (a Chrome flag), `brave` (Brave flags), `ios`, `https` or `browsers`. `unsupportedReason(kind)` returns the reason alone.
166
+ - `identifyPrinter(name)` matches a Bluetooth name to a known model across every family, from `PRINTER_CATALOG`; `tsplModelFor`, `escposModelFor` and `catModelFor` look in one family. `identifyUsbPrinter(device)` does the same for a USB device, by vendor and product ID, then product name.
167
+ - `UsbPrinter` connects over WebUSB to a printer-class device (`USB_PRINTER_CLASS`, 7). The family comes from the catalog, then from a type set with `unrecognisedAs` when the printer speaks it, then from the printer's IEEE 1284 device ID (`familiesFromDeviceId`). A printer the site already uses connects without the chooser when it is the only one plugged in; `rememberedUsbPrinters()` lists them. `usbTransport(device)` gives the raw `ThermalTransport`.
168
+ - `supportIssue(kind)` says why a connection type cannot be used in the current browser, as `{ reason, topic }`, or null when it can. `topic` names what fixes it: `linux` (a Chrome flag), `brave` (Brave flags), `ios`, `https`, `usb` (Windows and Linux drivers holding a USB printer) or `browsers`. `unsupportedReason(kind)` returns the reason alone.
164
169
  - `detectBrowser()` returns the system, the browser and whether it is built on Chromium.
165
- - `connectErrorTopic(err)` maps a connect error from a browser that has Bluetooth but cannot use it (adapter off, API turned off) to the same topics.
170
+ - `connectErrorTopic(err)` maps a connect error from a browser that has Bluetooth or USB but cannot use it (adapter off, API turned off, a system driver holding the printer) to the same topics.
166
171
 
167
172
  ## Browsers
168
173
 
169
- Needs Web Bluetooth or Web Serial: Chrome or Edge on desktop, or Chrome on Android. On iPhone and iPad, the Bluefy browser adds Web Bluetooth. On Linux, Chrome needs `chrome://flags/#enable-web-bluetooth` turned on; Brave keeps both APIs behind `brave://flags`.
174
+ Needs Web Bluetooth, Web Serial or WebUSB: Chrome or Edge on desktop, or Chrome on Android. On iPhone and iPad, the Bluefy browser adds Web Bluetooth. On Linux, Chrome needs `chrome://flags/#enable-web-bluetooth` turned on; Brave keeps Bluetooth and serial behind `brave://flags`. For USB printers, Windows needs the WinUSB driver (Zadig installs it), and Linux needs the `usblp` module unloaded and a udev rule granting access.
170
175
 
171
176
  ## Feedback
172
177
 
@@ -1,5 +1,19 @@
1
- import type { ClientLike } from './client.js';
1
+ import { type CatalogEntry } from './catalog.js';
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
@@ -9,55 +23,26 @@ type Lib = typeof import('@mmote/niimbluelib');
9
23
  * the TSPL client; and everything else the NIIMBOT client exactly as before.
10
24
  * Known names never overlap, so each printer matches one client at most.
11
25
  */
12
- export declare class BluetoothPrinter implements ClientLike {
26
+ export declare class BluetoothPrinter extends RoutedClient {
13
27
  private lib;
14
- private inner;
15
28
  constructor(lib: () => Promise<Lib>);
16
- private get client();
17
29
  /** The kind of printer to treat an unrecognised one as; null leaves it to NIIMBOT. */
18
30
  private fallback;
19
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;
20
37
  connect(): Promise<{
21
38
  deviceName?: string;
22
39
  }>;
23
- disconnect(): Promise<void>;
24
- isConnected(): boolean;
25
- on(event: string, fn: (e: any) => void): unknown;
26
- off(event: string, fn: (e: any) => void): unknown;
27
- getPrinterInfo(): {
28
- batteryPercents?: number;
29
- modelId?: number;
30
- printheadWidth?: number;
31
- protocolVersion?: number;
32
- };
33
- getModelMetadata(): import("@mmote/niimbluelib").PrinterModelMeta | undefined;
34
- getPrintTaskType(): "B1" | "B21_L2B" | "B21_V1" | "D110" | "D110M_V4" | "D11_V1" | "H1S" | undefined;
35
- getRfidInfo(): {
36
- labelRfidInfo?: {
37
- tagPresent?: boolean;
38
- barCode?: string;
39
- allPaper?: number;
40
- usedPaper?: number;
41
- consumablesType?: number;
42
- };
43
- paperInfo?: {
44
- valid?: boolean;
45
- paperWidth?: number;
46
- paperHeight?: number;
47
- paperType?: number;
48
- };
49
- };
50
- fetchRfidInfo(): Promise<unknown>;
51
- startHeartbeat(): void;
52
- stopHeartbeat(): void;
53
- isHeartbeatStarted(): boolean;
54
- get protocol(): {
55
- newPrintTask(name: import("@mmote/niimbluelib").PrintTaskName, options: Record<string, unknown>): import("./client.js").PrintTaskLike;
56
- printEnd(): Promise<boolean>;
57
- };
58
- get encodePage(): ((canvas: HTMLCanvasElement) => unknown) | undefined;
59
- get rollKey(): (() => string) | undefined;
60
- get feedPaper(): ((dots: number) => Promise<void>) | undefined;
61
- get feedsBack(): boolean | undefined;
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;
62
47
  }
63
48
  export {};
package/dist/catalog.d.ts CHANGED
@@ -1,4 +1,12 @@
1
- import { type ThermalModel } from './thermal.js';
1
+ /**
2
+ * Every printer outside NIIMBOT that libniimai knows, by Bluetooth name, in
3
+ * one lookup across families. A name matches one family at most, and a name
4
+ * that matches none is known to be unknown before anything is sent to it.
5
+ * The models themselves live with their family: ESCPOS_MODELS, TSPL_MODELS
6
+ * and CAT_MODELS.
7
+ */
8
+ import type { ClientLike } from './client.js';
9
+ import { type Pace, type ThermalModel, type ThermalTransport } from './thermal.js';
2
10
  export type PrinterFamily = 'escpos' | 'tspl' | 'cat';
3
11
  export interface CatalogEntry {
4
12
  family: PrinterFamily;
@@ -7,6 +15,18 @@ export interface CatalogEntry {
7
15
  export declare const PRINTER_CATALOG: CatalogEntry[];
8
16
  /** The known printer a Bluetooth name belongs to, longest prefix first across every family. */
9
17
  export declare function identifyPrinter(name: string | undefined): CatalogEntry | null;
18
+ /** The known printer behind a USB device: by its vendor and product IDs, then by its product name. */
19
+ export declare function identifyUsbPrinter({ vendorId, productId, productName }: {
20
+ vendorId: number;
21
+ productId: number;
22
+ productName?: string;
23
+ }): CatalogEntry | null;
24
+ /**
25
+ * The client for a thermal printer over a cable link. Cat printers are
26
+ * Bluetooth only, so they get null. `ackTimeoutMs`: how long a label printer
27
+ * waits for its "OK" before asking for its status.
28
+ */
29
+ export declare function cableClient({ family, model }: CatalogEntry, transport: ThermalTransport, pace: Pace, ackTimeoutMs: number): ClientLike | null;
10
30
  /** A kind of printer to pick for one niimai does not recognise. */
11
31
  export interface PrinterType extends CatalogEntry {
12
32
  id: string;
package/dist/client.d.ts CHANGED
@@ -60,16 +60,26 @@ 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
- export type ConnectionKind = 'bluetooth' | 'serial' | 'demo';
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. */
79
+ export type ConnectionKind = 'bluetooth' | 'serial' | 'usb' | 'demo';
71
80
  export declare const supportsBluetooth: () => boolean;
72
81
  export declare const supportsSerial: () => boolean;
82
+ export declare const supportsUsb: () => boolean;
73
83
  export type Platform = 'ios' | 'android' | 'desktop';
74
84
  export declare function detectPlatform(ua?: string, touchPoints?: number): Platform;
75
85
  export type OperatingSystem = 'ios' | 'android' | 'chromeos' | 'linux' | 'mac' | 'windows' | 'other';
@@ -77,7 +87,7 @@ export type BrowserName = 'chrome' | 'edge' | 'opera' | 'brave' | 'samsung' | 'b
77
87
  export interface BrowserInfo {
78
88
  os: OperatingSystem;
79
89
  browser: BrowserName;
80
- /** Built on Chromium, so Web Bluetooth and Web Serial can be there. */
90
+ /** Built on Chromium, so Web Bluetooth, Web Serial and WebUSB can be there. */
81
91
  chromium: boolean;
82
92
  /** Served over https or from localhost; browsers hide both APIs elsewhere. */
83
93
  secure: boolean;
@@ -98,8 +108,9 @@ export declare function detectBrowser(env?: Partial<BrowserEnv>): BrowserInfo;
98
108
  * - `ios`: iPhone and iPad browsers, and Bluefy.
99
109
  * - `https`: the page has to be served over https.
100
110
  * - `browsers`: which browsers work.
111
+ * - `usb`: Windows and Linux hold on to USB printers until told to let the browser have them.
101
112
  */
102
- export type SetupTopic = 'linux' | 'brave' | 'ios' | 'https' | 'browsers';
113
+ export type SetupTopic = 'linux' | 'brave' | 'ios' | 'https' | 'browsers' | 'usb';
103
114
  export interface SupportIssue {
104
115
  /** In a few words, what is missing or what to do. */
105
116
  reason: string;
@@ -113,8 +124,8 @@ export declare function supportIssue(kind: ConnectionKind, env?: Partial<Browser
113
124
  /** supportIssue's reason alone. */
114
125
  export declare function unsupportedReason(kind: ConnectionKind, platform?: Platform): string | null;
115
126
  /**
116
- * Errors browsers raise when Bluetooth is there but cannot be used: the
117
- * adapter is off or missing, or the browser turned the API off. The Help topic
118
- * to point to, or null.
127
+ * Errors browsers raise when Bluetooth or USB is there but cannot be used:
128
+ * the adapter is off or missing, the browser turned the API off, or the
129
+ * system keeps a USB printer for itself. The Help topic to point to, or null.
119
130
  */
120
131
  export declare function connectErrorTopic(err: unknown): SetupTopic | null;
package/dist/escpos.d.ts CHANGED
@@ -44,8 +44,12 @@ 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 readonly headDots: number;
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>;
51
55
  /** These printers cannot sense the paper, so the user lines it up by hand. */
package/dist/index.d.ts CHANGED
@@ -1,11 +1,13 @@
1
1
  /// <reference types="web-bluetooth" preserve="true" />
2
2
  /// <reference types="w3c-web-serial" preserve="true" />
3
+ /// <reference types="w3c-web-usb" preserve="true" />
3
4
  import type { ClientLike, ConnectionKind } from './client.js';
4
5
  import { PrinterService, type ClientFactory } from './service.js';
5
6
  /**
6
7
  * A client for one kind of connection. Bluetooth finds the printer family
7
- * from the device the user picks; serial is NIIMBOT only; demo is a printer
8
- * in memory.
8
+ * from the device the user picks; serial is NIIMBOT only; usb is for label
9
+ * and receipt printers that show up as standard USB printers; demo is a
10
+ * printer in memory.
9
11
  */
10
12
  export declare function createClient(kind: ConnectionKind): Promise<ClientLike>;
11
13
  export interface CreatePrinterOptions {
@@ -19,15 +21,16 @@ export interface CreatePrinterOptions {
19
21
  export declare function createPrinter(options?: CreatePrinterOptions): PrinterService;
20
22
  export { PrinterService, DEMO_NAME, friendlyError, fitToPrinthead, rotateCanvas, shiftCanvas } from './service.js';
21
23
  export type { ClientFactory, PageSource, PrinterState, PrinterStatus, PrintOffset, PrintOptions, Rotation } from './service.js';
22
- export { connectErrorTopic, detectBrowser, detectPlatform, supportIssue, supportsBluetooth, supportsSerial, unsupportedReason } from './client.js';
24
+ export { connectErrorTopic, detectBrowser, detectPlatform, supportIssue, supportsBluetooth, supportsSerial, supportsUsb, unsupportedReason } from './client.js';
23
25
  export type { BrowserEnv, BrowserInfo, BrowserName, ClientLike, ConnectionKind, OperatingSystem, Platform, PrintTaskLike, SetupTopic, SupportIssue } from './client.js';
24
26
  export { correctTask, findManualProfile, inkOutsideHead, MANUAL_PROFILES, printheadCrop, printheadMm, resolveProfile } from './profiles.js';
25
27
  export type { LabelSize, PrintDirection, PrinterProfile } from './profiles.js';
26
28
  export { effectivePaper, labelTypeOf, PAPER_TYPES, paperFromLabelType, paperName } from './paper.js';
27
29
  export type { PaperType } from './paper.js';
28
- export { findPrinterType, identifyPrinter, PRINTER_CATALOG, PRINTER_TYPES } from './catalog.js';
30
+ export { findPrinterType, identifyPrinter, identifyUsbPrinter, PRINTER_CATALOG, PRINTER_TYPES } from './catalog.js';
29
31
  export type { CatalogEntry, PrinterFamily, PrinterType } from './catalog.js';
30
- export { BluetoothPrinter } from './bluetooth.js';
32
+ export { BluetoothPrinter, byServices, type MatchedFamily, type MatchedPrinters } from './bluetooth.js';
33
+ export { familiesFromDeviceId, parseDeviceId, rememberedUsbPrinters, UsbPrinter, usbTransport, USB_PRINTER_CLASS } from './usb.js';
31
34
  export { describeBleError } from './ble.js';
32
35
  export { DEMO_META, MockClient } from './mock.js';
33
36
  export { modelFor, THERMAL_DEFAULT_SIZE, THERMAL_HEAD_DOTS, THERMAL_HEAD_MM, THERMAL_SIZES, ThermalPrinterClient, webTransport } from './thermal.js';