libniimai 0.4.0 → 0.6.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/dist/libniimai.js CHANGED
@@ -227,7 +227,7 @@ var ThermalPrinterClient = class {
227
227
  constructor(transport, pace, firstChunk) {
228
228
  this.transport = transport;
229
229
  this.pace = pace;
230
- this.chunk = firstChunk;
230
+ this.chunk = transport.chunkBytes ?? firstChunk;
231
231
  transport.onDisconnect(() => {
232
232
  this.connected = false;
233
233
  this.emit("disconnect", {});
@@ -868,7 +868,8 @@ var DOTS_PER_MM = 8;
868
868
  var TSPL_MODELS = [{
869
869
  prefix: "QR380A",
870
870
  confidence: "tested",
871
- note: "Sold as CC390"
871
+ note: "Sold as CC390",
872
+ usbIds: [[10473, 649]]
872
873
  }, {
873
874
  prefix: "GP-M322",
874
875
  confidence: "medium",
@@ -1033,13 +1034,6 @@ var TsplPrinterClient = class extends ThermalPrinterClient {
1033
1034
  }
1034
1035
  };
1035
1036
  // src/catalog.ts
1036
- /**
1037
- * Every printer outside NIIMBOT that libniimai knows, by Bluetooth name, in
1038
- * one lookup across families. A name matches one family at most, and a name
1039
- * that matches none is known to be unknown before anything is sent to it.
1040
- * The models themselves live with their family: ESCPOS_MODELS, TSPL_MODELS
1041
- * and CAT_MODELS.
1042
- */
1043
1037
  var PRINTER_CATALOG = [
1044
1038
  ...ESCPOS_MODELS.map((model) => ({
1045
1039
  family: "escpos",
@@ -1061,6 +1055,20 @@ function identifyPrinter(name) {
1061
1055
  for (const entry of PRINTER_CATALOG) if (name.startsWith(entry.model.prefix) && entry.model.prefix.length > (best?.model.prefix.length ?? 0)) best = entry;
1062
1056
  return best;
1063
1057
  }
1058
+ /** The known printer behind a USB device: by its vendor and product IDs, then by its product name. */
1059
+ function identifyUsbPrinter({ vendorId, productId, productName }) {
1060
+ return PRINTER_CATALOG.find((e) => e.model.usbIds?.some(([v, p]) => v === vendorId && p === productId)) ?? identifyPrinter(productName);
1061
+ }
1062
+ /**
1063
+ * The client for a thermal printer over a cable link. Cat printers are
1064
+ * Bluetooth only, so they get null. `ackTimeoutMs`: how long a label printer
1065
+ * waits for its "OK" before asking for its status.
1066
+ */
1067
+ function cableClient({ family, model }, transport, pace, ackTimeoutMs) {
1068
+ if (family === "tspl") return new TsplPrinterClient(transport, model, pace, ackTimeoutMs);
1069
+ if (family === "escpos") return new EscPosPrinterClient(transport, model, pace);
1070
+ return null;
1071
+ }
1064
1072
  /** The kinds of printer an unrecognised one can be set to, alongside NIIMBOT's MANUAL_PROFILES. */
1065
1073
  var PRINTER_TYPES = [
1066
1074
  {
@@ -1108,6 +1116,70 @@ var PRINTER_TYPES = [
1108
1116
  }
1109
1117
  ];
1110
1118
  var findPrinterType = (id) => PRINTER_TYPES.find((t) => t.id === id) ?? null;
1119
+ // src/routed.ts
1120
+ /**
1121
+ * A client that picks the real client when it connects, then passes
1122
+ * everything through to it: the Bluetooth and USB printers behind the
1123
+ * Connect buttons.
1124
+ */
1125
+ var RoutedClient = class {
1126
+ inner = null;
1127
+ get client() {
1128
+ if (!this.inner) throw new Error("Not connected");
1129
+ return this.inner;
1130
+ }
1131
+ async disconnect() {
1132
+ await this.inner?.disconnect();
1133
+ }
1134
+ isConnected() {
1135
+ return !!this.inner?.isConnected();
1136
+ }
1137
+ on(event, fn) {
1138
+ return this.client.on(event, fn);
1139
+ }
1140
+ off(event, fn) {
1141
+ return this.inner?.off(event, fn);
1142
+ }
1143
+ getPrinterInfo() {
1144
+ return this.inner?.getPrinterInfo() ?? {};
1145
+ }
1146
+ getModelMetadata() {
1147
+ return this.inner?.getModelMetadata();
1148
+ }
1149
+ getPrintTaskType() {
1150
+ return this.inner?.getPrintTaskType();
1151
+ }
1152
+ getRfidInfo() {
1153
+ return this.inner?.getRfidInfo() ?? {};
1154
+ }
1155
+ fetchRfidInfo() {
1156
+ return this.client.fetchRfidInfo();
1157
+ }
1158
+ startHeartbeat() {
1159
+ this.inner?.startHeartbeat();
1160
+ }
1161
+ stopHeartbeat() {
1162
+ this.inner?.stopHeartbeat();
1163
+ }
1164
+ isHeartbeatStarted() {
1165
+ return !!this.inner?.isHeartbeatStarted?.();
1166
+ }
1167
+ get protocol() {
1168
+ return this.client.protocol;
1169
+ }
1170
+ get encodePage() {
1171
+ return this.inner?.encodePage?.bind(this.inner);
1172
+ }
1173
+ get rollKey() {
1174
+ return this.inner?.rollKey?.bind(this.inner);
1175
+ }
1176
+ get feedPaper() {
1177
+ return this.inner?.feedPaper?.bind(this.inner);
1178
+ }
1179
+ get feedsBack() {
1180
+ return this.inner?.feedsBack;
1181
+ }
1182
+ };
1111
1183
  // src/bluetooth.ts
1112
1184
  /**
1113
1185
  * A writable characteristic on one of the serial services budget printers
@@ -1124,7 +1196,7 @@ async function writeChannel(server, services) {
1124
1196
  }
1125
1197
  return null;
1126
1198
  }
1127
- var PRINTER_TYPE_NAMES = {
1199
+ var PRINTER_TYPE_NAMES$1 = {
1128
1200
  escpos: "receipt printer",
1129
1201
  tspl: "label printer",
1130
1202
  cat: "cat printer"
@@ -1164,16 +1236,12 @@ function looksNiimbot(lib, name) {
1164
1236
  * the TSPL client; and everything else the NIIMBOT client exactly as before.
1165
1237
  * Known names never overlap, so each printer matches one client at most.
1166
1238
  */
1167
- var BluetoothPrinter = class {
1239
+ var BluetoothPrinter = class extends RoutedClient {
1168
1240
  lib;
1169
- inner = null;
1170
1241
  constructor(lib) {
1242
+ super();
1171
1243
  this.lib = lib;
1172
1244
  }
1173
- get client() {
1174
- if (!this.inner) throw new Error("Not connected");
1175
- return this.inner;
1176
- }
1177
1245
  /** The kind of printer to treat an unrecognised one as; null leaves it to NIIMBOT. */
1178
1246
  fallback = null;
1179
1247
  unrecognisedAs(typeId) {
@@ -1189,7 +1257,7 @@ var BluetoothPrinter = class {
1189
1257
  const services = (await server.getPrimaryServices().catch(() => [])).map((s) => s.uuid);
1190
1258
  this.inner = await thermalClient(device, server, services, entry);
1191
1259
  if (this.inner) return this.inner.connect();
1192
- if (!known) throw new Error(`${device.name ?? "This printer"} does not offer a ${PRINTER_TYPE_NAMES[entry.family]} service`);
1260
+ if (!known) throw new Error(`${device.name ?? "This printer"} does not offer a ${PRINTER_TYPE_NAMES$1[entry.family]} service`);
1193
1261
  }
1194
1262
  const lib = await this.lib();
1195
1263
  this.inner = createBleClient(lib, device);
@@ -1200,57 +1268,6 @@ var BluetoothPrinter = class {
1200
1268
  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.`);
1201
1269
  }
1202
1270
  }
1203
- async disconnect() {
1204
- await this.inner?.disconnect();
1205
- }
1206
- isConnected() {
1207
- return !!this.inner?.isConnected();
1208
- }
1209
- on(event, fn) {
1210
- return this.client.on(event, fn);
1211
- }
1212
- off(event, fn) {
1213
- return this.inner?.off(event, fn);
1214
- }
1215
- getPrinterInfo() {
1216
- return this.inner?.getPrinterInfo() ?? {};
1217
- }
1218
- getModelMetadata() {
1219
- return this.inner?.getModelMetadata();
1220
- }
1221
- getPrintTaskType() {
1222
- return this.inner?.getPrintTaskType();
1223
- }
1224
- getRfidInfo() {
1225
- return this.inner?.getRfidInfo() ?? {};
1226
- }
1227
- fetchRfidInfo() {
1228
- return this.client.fetchRfidInfo();
1229
- }
1230
- startHeartbeat() {
1231
- this.inner?.startHeartbeat();
1232
- }
1233
- stopHeartbeat() {
1234
- this.inner?.stopHeartbeat();
1235
- }
1236
- isHeartbeatStarted() {
1237
- return !!this.inner?.isHeartbeatStarted?.();
1238
- }
1239
- get protocol() {
1240
- return this.client.protocol;
1241
- }
1242
- get encodePage() {
1243
- return this.inner?.encodePage?.bind(this.inner);
1244
- }
1245
- get rollKey() {
1246
- return this.inner?.rollKey?.bind(this.inner);
1247
- }
1248
- get feedPaper() {
1249
- return this.inner?.feedPaper?.bind(this.inner);
1250
- }
1251
- get feedsBack() {
1252
- return this.inner?.feedsBack;
1253
- }
1254
1271
  };
1255
1272
  // src/mock.ts
1256
1273
  /** A printer that only exists in memory. Used by the demo mode and the tests. */
@@ -1621,7 +1638,7 @@ function withTimeout(p, ms) {
1621
1638
  /** Translates the library's error codes and messages into short, readable ones. */
1622
1639
  function friendlyError(err) {
1623
1640
  const msg = err instanceof Error ? err.message : String(err);
1624
- if (/user cancelled|no device selected|chooser/i.test(msg)) return "Connection cancelled";
1641
+ if (/user cancelled|no (device|port) selected|chooser/i.test(msg)) return "Connection cancelled";
1625
1642
  if (/timeout waiting for the printer to finish/i.test(msg)) return "The printer did not confirm the print";
1626
1643
  if (/timeout/i.test(msg)) return "Printer stopped responding";
1627
1644
  if (/lid|cover/i.test(msg)) return "Close the printer lid";
@@ -1883,43 +1900,299 @@ var PrinterService = class {
1883
1900
  // src/client.ts
1884
1901
  var supportsBluetooth = () => typeof navigator !== "undefined" && "bluetooth" in navigator;
1885
1902
  var supportsSerial = () => typeof navigator !== "undefined" && "serial" in navigator;
1903
+ var supportsUsb = () => typeof navigator !== "undefined" && "usb" in navigator;
1904
+ var SUPPORTS = {
1905
+ bluetooth: supportsBluetooth,
1906
+ serial: supportsSerial,
1907
+ usb: supportsUsb
1908
+ };
1886
1909
  function detectPlatform(ua = typeof navigator === "undefined" ? "" : navigator.userAgent, touchPoints = typeof navigator === "undefined" ? 0 : navigator.maxTouchPoints) {
1887
1910
  if (/iPhone|iPad|iPod/.test(ua) || /Macintosh/.test(ua) && touchPoints > 1) return "ios";
1888
1911
  if (/Android/.test(ua)) return "android";
1889
1912
  return "desktop";
1890
1913
  }
1914
+ function currentEnv() {
1915
+ if (typeof navigator === "undefined") return {
1916
+ ua: "",
1917
+ touchPoints: 0,
1918
+ brave: false,
1919
+ secure: true
1920
+ };
1921
+ return {
1922
+ ua: navigator.userAgent,
1923
+ touchPoints: navigator.maxTouchPoints,
1924
+ brave: "brave" in navigator,
1925
+ secure: typeof window === "undefined" || window.isSecureContext !== false
1926
+ };
1927
+ }
1928
+ function detectBrowser(env = {}) {
1929
+ const { ua, touchPoints, brave, secure } = {
1930
+ ...currentEnv(),
1931
+ ...env
1932
+ };
1933
+ const platform = detectPlatform(ua, touchPoints);
1934
+ const os = 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";
1935
+ return {
1936
+ os,
1937
+ browser: brave ? "brave" : /Bluefy/i.test(ua) ? "bluefy" : /Firefox|FxiOS/.test(ua) ? "firefox" : /Edg(e|A|iOS)?\//.test(ua) ? "edge" : /OPR\/|Opera/.test(ua) ? "opera" : /SamsungBrowser/.test(ua) ? "samsung" : /Chrome\/|CriOS|Chromium/.test(ua) ? "chrome" : /Safari\//.test(ua) ? "safari" : "other",
1938
+ chromium: os !== "ios" && /Chrome\/|Chromium/.test(ua),
1939
+ secure
1940
+ };
1941
+ }
1942
+ var BROWSER_NAMES = {
1943
+ firefox: "Firefox",
1944
+ safari: "Safari",
1945
+ samsung: "Samsung Internet",
1946
+ opera: "Opera"
1947
+ };
1891
1948
  /**
1892
- * Why a connection type cannot be used in this browser, in a few words, or
1893
- * null when it can. iOS browsers all run on WebKit, which has neither Web
1894
- * Bluetooth nor Web Serial; Bluefy adds Web Bluetooth there.
1949
+ * Why a connection type cannot be used in this browser, and where to read how
1950
+ * to fix it, or null when it can be used.
1895
1951
  */
1896
- function unsupportedReason(kind, platform = detectPlatform()) {
1952
+ function supportIssue(kind, env = {}) {
1897
1953
  if (kind === "demo") return null;
1898
- if (kind === "bluetooth" ? supportsBluetooth() : supportsSerial()) return null;
1899
- if (platform === "ios") return kind === "bluetooth" ? "On iPhone and iPad, use the Bluefy browser" : "Not available on iPhone and iPad";
1900
- if (platform === "android") return "Open niimai in Chrome";
1901
- return "Needs Chrome or Edge";
1954
+ if (SUPPORTS[kind]()) return null;
1955
+ const { os, browser, chromium, secure } = detectBrowser(env);
1956
+ const bluetooth = kind === "bluetooth";
1957
+ if (os === "ios") return {
1958
+ reason: bluetooth ? "On iPhone and iPad, use the Bluefy browser" : "Not available on iPhone and iPad",
1959
+ topic: "ios"
1960
+ };
1961
+ if (!secure && chromium) return {
1962
+ reason: "Needs an https page",
1963
+ topic: "https"
1964
+ };
1965
+ if (browser === "brave") return {
1966
+ reason: "Turn it on in Brave flags",
1967
+ topic: "brave"
1968
+ };
1969
+ if (os === "android") {
1970
+ if (!bluetooth) return {
1971
+ reason: "Needs Chrome or Edge on a computer",
1972
+ topic: "browsers"
1973
+ };
1974
+ return {
1975
+ reason: browser === "chrome" ? "Update Chrome" : "Open niimai in Chrome",
1976
+ topic: "browsers"
1977
+ };
1978
+ }
1979
+ if (bluetooth && os === "linux" && chromium) return {
1980
+ reason: "Needs a Chrome flag on Linux",
1981
+ topic: "linux"
1982
+ };
1983
+ const name = BROWSER_NAMES[browser];
1984
+ if (name) return {
1985
+ reason: `${name} has no ${bluetooth ? "Bluetooth" : "USB"} access; use Chrome or Edge`,
1986
+ topic: "browsers"
1987
+ };
1988
+ if (chromium) return {
1989
+ reason: "Turned off in this browser",
1990
+ topic: "browsers"
1991
+ };
1992
+ return {
1993
+ reason: "Needs Chrome or Edge",
1994
+ topic: "browsers"
1995
+ };
1996
+ }
1997
+ /** Stand-ins for the platform-only signature below. */
1998
+ var PLATFORM_ENVS = {
1999
+ ios: {
2000
+ ua: "iPhone",
2001
+ touchPoints: 5,
2002
+ brave: false
2003
+ },
2004
+ android: {
2005
+ ua: "Android Firefox",
2006
+ touchPoints: 5,
2007
+ brave: false
2008
+ },
2009
+ desktop: {
2010
+ ua: "",
2011
+ touchPoints: 0,
2012
+ brave: false
2013
+ }
2014
+ };
2015
+ /** supportIssue's reason alone. */
2016
+ function unsupportedReason(kind, platform) {
2017
+ return supportIssue(kind, platform ? PLATFORM_ENVS[platform] : {})?.reason ?? null;
2018
+ }
2019
+ /**
2020
+ * Errors browsers raise when Bluetooth or USB is there but cannot be used:
2021
+ * the adapter is off or missing, the browser turned the API off, or the
2022
+ * system keeps a USB printer for itself. The Help topic to point to, or null.
2023
+ */
2024
+ function connectErrorTopic(err) {
2025
+ const message = err instanceof Error ? err.message : String(err);
2026
+ if (/globally disabled/i.test(message)) return "brave";
2027
+ if (/claim interface|Access denied|Unable to open device/i.test(message)) return "usb";
2028
+ if (/adapter not available|Bluetooth adapter|Bluetooth is (turned off|disabled|not available)/i.test(message)) return detectBrowser().os === "linux" ? "linux" : "browsers";
2029
+ return null;
1902
2030
  }
2031
+ // src/usb.ts
2032
+ /**
2033
+ * Label and receipt printers on a USB cable that show up as standard USB
2034
+ * printers (interface class 7), which Web Serial cannot see. They take the
2035
+ * same TSPL or ESC/POS bytes as over Bluetooth, through WebUSB. NIIMBOT
2036
+ * printers show up as serial ports and keep using Web Serial.
2037
+ */
2038
+ var USB_PRINTER_CLASS = 7;
2039
+ /** USB moves data far faster than Bluetooth and paces itself, so writes are large and back to back. */
2040
+ var USB_PACE = {
2041
+ chunkMs: 0,
2042
+ pauseMs: 100
2043
+ };
2044
+ var USB_CHUNK = 4096;
2045
+ /** Label printers may not send "OK" over USB; a status query after this long tells a fault from a quiet printer. */
2046
+ var USB_ACK_TIMEOUT_MS = 5e3;
2047
+ /**
2048
+ * The fields of an IEEE 1284 device ID, keys in capitals:
2049
+ * "MFG:ACME;COMMAND SET:TSC/ESC/POS;MODEL:CC390;" → { MFG, 'COMMAND SET', MODEL }.
2050
+ */
2051
+ function parseDeviceId(text) {
2052
+ const fields = {};
2053
+ for (const part of text.split(";")) {
2054
+ const at = part.indexOf(":");
2055
+ if (at > 0) fields[part.slice(0, at).trim().toUpperCase()] = part.slice(at + 1).trim();
2056
+ }
2057
+ return fields;
2058
+ }
2059
+ /** The printer languages a device ID names, TSPL first: label printers often take ESC/POS as well. */
2060
+ function familiesFromDeviceId(text) {
2061
+ const id = parseDeviceId(text);
2062
+ const commands = id.CMD ?? id["COMMAND SET"] ?? "";
2063
+ const families = [];
2064
+ if (/\b(TSPL2?|TSC)\b/i.test(commands)) families.push("tspl");
2065
+ if (/ESC\/?POS|\bEPSON\b/i.test(commands)) families.push("escpos");
2066
+ return families;
2067
+ }
2068
+ var isPrinter = (device) => device.configuration?.interfaces.some((i) => i.alternates.some((a) => a.interfaceClass === 7)) ?? true;
2069
+ /** USB printers this site may already use that are plugged in. */
2070
+ async function rememberedUsbPrinters() {
2071
+ if (!supportsUsb()) return [];
2072
+ return (await navigator.usb.getDevices().catch(() => [])).filter(isPrinter);
2073
+ }
2074
+ /** Opens a USB printer and claims its printer interface. Also reads the printer's device ID. */
2075
+ async function usbTransport(device) {
2076
+ await device.open();
2077
+ if (!device.configuration) await device.selectConfiguration(1);
2078
+ const iface = device.configuration.interfaces.find((i) => i.alternates.some((a) => a.interfaceClass === 7));
2079
+ if (!iface) throw new Error(`${device.productName ?? "This device"} is not a USB printer`);
2080
+ const alt = iface.alternates.find((a) => a.interfaceClass === 7);
2081
+ await device.claimInterface(iface.interfaceNumber);
2082
+ if (alt.alternateSetting !== 0) await device.selectAlternateInterface(iface.interfaceNumber, alt.alternateSetting);
2083
+ const out = alt.endpoints.find((e) => e.direction === "out" && e.type === "bulk");
2084
+ const back = alt.endpoints.find((e) => e.direction === "in" && e.type === "bulk");
2085
+ if (!out) throw new Error(`${device.productName ?? "This printer"} has no way to send it data`);
2086
+ const deviceId = await device.controlTransferIn({
2087
+ requestType: "class",
2088
+ recipient: "interface",
2089
+ request: 0,
2090
+ value: 0,
2091
+ index: iface.interfaceNumber << 8 | alt.alternateSetting
2092
+ }, 1024).then((r) => r.data ? new TextDecoder().decode(new Uint8Array(r.data.buffer, r.data.byteOffset + 2, Math.max(0, r.data.byteLength - 2))) : "").catch(() => "");
2093
+ const notify = [];
2094
+ const gone = [];
2095
+ let open = true;
2096
+ if (back) (async () => {
2097
+ while (open) {
2098
+ const r = await device.transferIn(back.endpointNumber, back.packetSize).catch(() => null);
2099
+ if (!r) break;
2100
+ if (r.data?.byteLength) notify.forEach((fn) => fn(new Uint8Array(r.data.buffer.slice(r.data.byteOffset, r.data.byteOffset + r.data.byteLength))));
2101
+ }
2102
+ })();
2103
+ const onUnplug = (e) => {
2104
+ if (e.device !== device) return;
2105
+ open = false;
2106
+ navigator.usb.removeEventListener("disconnect", onUnplug);
2107
+ gone.forEach((fn) => fn());
2108
+ };
2109
+ navigator.usb.addEventListener("disconnect", onUnplug);
2110
+ return {
2111
+ deviceId,
2112
+ transport: {
2113
+ name: device.productName || void 0,
2114
+ chunkBytes: USB_CHUNK,
2115
+ write: async (chunk) => {
2116
+ if ((await device.transferOut(out.endpointNumber, chunk.slice())).status !== "ok") throw new Error("The printer did not take the data");
2117
+ },
2118
+ onNotify: (fn) => notify.push(fn),
2119
+ onDisconnect: (fn) => gone.push(fn),
2120
+ disconnect: async () => {
2121
+ open = false;
2122
+ navigator.usb.removeEventListener("disconnect", onUnplug);
2123
+ await device.releaseInterface(iface.interfaceNumber).catch(() => {});
2124
+ await device.close().catch(() => {});
2125
+ }
2126
+ }
2127
+ };
2128
+ }
2129
+ var PRINTER_TYPE_NAMES = {
2130
+ escpos: "receipt printer",
2131
+ tspl: "label printer",
2132
+ cat: "cat printer"
2133
+ };
2134
+ /**
2135
+ * The USB printer behind the USB button for label and receipt printers. A
2136
+ * printer this site already uses connects straight away when it is the only
2137
+ * one plugged in; otherwise the browser lists USB printers. The printer's
2138
+ * language comes from the catalog, then from the type picked under Advanced
2139
+ * when the printer speaks it (or does not say), then from the device ID the
2140
+ * printer reports.
2141
+ */
2142
+ var UsbPrinter = class extends RoutedClient {
2143
+ fallback = null;
2144
+ unrecognisedAs(typeId) {
2145
+ this.fallback = findPrinterType(typeId);
2146
+ }
2147
+ async connect() {
2148
+ const remembered = await rememberedUsbPrinters();
2149
+ const device = remembered.length === 1 ? remembered[0] : await navigator.usb.requestDevice({ filters: [{ classCode: 7 }] });
2150
+ const { transport, deviceId } = await usbTransport(device);
2151
+ const name = device.productName || "This printer";
2152
+ const families = familiesFromDeviceId(deviceId);
2153
+ const picked = this.fallback && (!families.length || families.includes(this.fallback.family)) ? this.fallback : null;
2154
+ const entry = identifyUsbPrinter(device) ?? picked ?? (families.length ? {
2155
+ family: families[0],
2156
+ model: {
2157
+ prefix: "",
2158
+ confidence: "medium",
2159
+ generic: true
2160
+ }
2161
+ } : null);
2162
+ if (!entry) {
2163
+ await transport.disconnect();
2164
+ throw new Error(`${name} did not say which printer language it speaks. Pick its type under Advanced and connect again.`);
2165
+ }
2166
+ this.inner = cableClient(entry, transport, USB_PACE, USB_ACK_TIMEOUT_MS);
2167
+ if (!this.inner) {
2168
+ await transport.disconnect();
2169
+ throw new Error(`A ${PRINTER_TYPE_NAMES[entry.family]} cannot print over USB`);
2170
+ }
2171
+ return this.inner.connect();
2172
+ }
2173
+ };
1903
2174
  // src/index.ts
1904
2175
  /**
1905
2176
  * libniimai: connect to NIIMBOT, TSPL, ESC/POS and cat printers from the
1906
- * browser over Web Bluetooth or Web Serial, and print a canvas on them.
2177
+ * browser over Web Bluetooth, Web Serial or WebUSB, and print a canvas on them.
1907
2178
  */
1908
2179
  var niimbot = () => import("@mmote/niimbluelib");
1909
2180
  /**
1910
2181
  * A client for one kind of connection. Bluetooth finds the printer family
1911
- * from the device the user picks; serial is NIIMBOT only; demo is a printer
1912
- * in memory.
2182
+ * from the device the user picks; serial is NIIMBOT only; usb is for label
2183
+ * and receipt printers that show up as standard USB printers; demo is a
2184
+ * printer in memory.
1913
2185
  */
1914
2186
  async function createClient(kind) {
1915
2187
  if (kind === "demo") return new MockClient();
1916
2188
  if (kind === "bluetooth") return new BluetoothPrinter(niimbot);
2189
+ if (kind === "usb") return new UsbPrinter();
1917
2190
  return new (await (niimbot())).NiimbotSerialClient();
1918
2191
  }
1919
2192
  /** A ready printer service: connect, watch its status, print canvases. */
1920
2193
  function createPrinter(options = {}) {
1921
2194
  return new PrinterService(options.createClient ?? createClient, async (canvas, direction) => (await niimbot()).ImageEncoder.encodeCanvas(canvas, 0, direction));
1922
2195
  }
1923
- 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, catFeed, printJob as catJob, catModelFor, correctTask, createClient, createPrinter, describeBleError, detectPlatform, effectivePaper, escposFeed, escposJob, escposModelFor, findManualProfile, findPrinterType, fitToPrinthead, friendlyError, identifyPrinter, inkOutsideHead, labelTypeOf, modelFor, paperFromLabelType, paperName, printheadCrop, printheadMm, resolveProfile, rotateCanvas, shiftCanvas, supportsBluetooth, supportsSerial, tsplJob, tsplModelFor, unsupportedReason, webTransport };
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 };
1924
2197
 
1925
2198
  //# sourceMappingURL=libniimai.js.map