@angadie/chittie-transport-web 0.1.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/LICENSE +24 -0
- package/README.md +66 -0
- package/dist/index.d.mts +39 -0
- package/dist/index.mjs +113 -0
- package/package.json +28 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Asyncdot Engineering
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
Portions of this software are vendored from MIT-licensed projects and retain
|
|
16
|
+
their original copyright notices; see VENDOR.md.
|
|
17
|
+
|
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# @angadie/chittie-transport-web
|
|
2
|
+
|
|
3
|
+
Web transports for chittie: **Web Serial**, **WebUSB**, and **Web Bluetooth**. Browser platform APIs only — no third-party dependencies. Each factory returns a [`Transport`](../chittie-transport).
|
|
4
|
+
|
|
5
|
+
> Web Serial / WebUSB / Web Bluetooth require a Chromium browser (Chrome/Edge desktop) over HTTPS, and `connect()` must be called from a user gesture (click).
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @angadie/chittie-transport-web @angadie/chittie-transport
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Web Serial (most desktop POS printers)
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { print } from '@angadie/chittie-transport';
|
|
17
|
+
import { createWebSerialTransport } from '@angadie/chittie-transport-web';
|
|
18
|
+
|
|
19
|
+
const transport = createWebSerialTransport({ baudRate: 9600 });
|
|
20
|
+
button.onclick = () => print(transport, bytes); // connect() prompts for the port
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`createWebSerialTransport` reconnects silently to a previously-granted port; otherwise it prompts.
|
|
24
|
+
|
|
25
|
+
## Web Bluetooth (BLE printers)
|
|
26
|
+
|
|
27
|
+
You supply the printer's GATT service + characteristic UUIDs:
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { createWebBluetoothTransport } from '@angadie/chittie-transport-web';
|
|
31
|
+
|
|
32
|
+
const transport = createWebBluetoothTransport({
|
|
33
|
+
serviceUuid: 0x18f0,
|
|
34
|
+
characteristicUuid: 0x2af1,
|
|
35
|
+
namePrefix: 'Printer', // optional device filter
|
|
36
|
+
chunkSize: 180, // BLE write size
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Bridge (USB / network printers a browser can't reach)
|
|
41
|
+
|
|
42
|
+
Browsers can't open USB printer-class devices or raw TCP. Run the [`chittie print-agent`](../../tools/print-agent) on the terminal and POST bytes to it:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import { print } from '@angadie/chittie-transport';
|
|
46
|
+
import { createBridgeTransport } from '@angadie/chittie-transport-web';
|
|
47
|
+
|
|
48
|
+
const transport = createBridgeTransport({ url: 'http://localhost:8930', token: 'secret', printer: 'usb' });
|
|
49
|
+
await print(transport, bytes); // connect() pings /health, write() POSTs /print
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The agent raw-prints to the OS queue (Windows winspool / CUPS) or, in virtual mode, renders a PNG.
|
|
53
|
+
|
|
54
|
+
## WebUSB
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { createWebUsbTransport } from '@angadie/chittie-transport-web';
|
|
58
|
+
|
|
59
|
+
const transport = createWebUsbTransport({ filters: [{ vendorId: 0x0416 }] });
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Claims the device's first interface and writes to its first OUT endpoint. Some printers need a specific interface/endpoint — open an issue if yours differs.
|
|
63
|
+
|
|
64
|
+
## License
|
|
65
|
+
|
|
66
|
+
MIT.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Transport } from "@angadie/chittie-transport";
|
|
2
|
+
|
|
3
|
+
//#region src/index.d.ts
|
|
4
|
+
/** Web Serial (Chrome/Edge desktop). Reconnects to a previously-granted port silently. */
|
|
5
|
+
interface WebSerialOptions {
|
|
6
|
+
baudRate?: number;
|
|
7
|
+
}
|
|
8
|
+
declare function createWebSerialTransport(options?: WebSerialOptions): Transport;
|
|
9
|
+
/** Web Bluetooth (GATT). You supply the printer's service + characteristic UUIDs. */
|
|
10
|
+
interface WebBluetoothOptions {
|
|
11
|
+
serviceUuid: string | number;
|
|
12
|
+
characteristicUuid: string | number;
|
|
13
|
+
namePrefix?: string;
|
|
14
|
+
/** BLE write size (default 180). */
|
|
15
|
+
chunkSize?: number;
|
|
16
|
+
}
|
|
17
|
+
declare function createWebBluetoothTransport(options: WebBluetoothOptions): Transport;
|
|
18
|
+
/** WebUSB — claims the first interface and writes to its first OUT endpoint. */
|
|
19
|
+
interface WebUsbOptions {
|
|
20
|
+
filters?: unknown[];
|
|
21
|
+
}
|
|
22
|
+
declare function createWebUsbTransport(options?: WebUsbOptions): Transport;
|
|
23
|
+
/** Print-agent bridge — POST bytes to a localhost agent that drives a USB/queue printer. */
|
|
24
|
+
interface BridgeOptions {
|
|
25
|
+
/** Agent base URL (default `http://localhost:8930`). */
|
|
26
|
+
url?: string;
|
|
27
|
+
/** Shared secret — sent as the `x-agent-token` header if set. */
|
|
28
|
+
token?: string;
|
|
29
|
+
/** Target OS print queue / `usb`; the agent's default if omitted. */
|
|
30
|
+
printer?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Transport that POSTs ESC/POS bytes to the chittie print-agent (`tools/print-agent`)
|
|
34
|
+
* — the way a browser reaches a USB printer-class device (or any OS queue) that it
|
|
35
|
+
* can't open directly. `connect()` pings `/health`; `write()` POSTs to `/print`.
|
|
36
|
+
*/
|
|
37
|
+
declare function createBridgeTransport(options?: BridgeOptions): Transport;
|
|
38
|
+
//#endregion
|
|
39
|
+
export { BridgeOptions, WebBluetoothOptions, WebSerialOptions, WebUsbOptions, createBridgeTransport, createWebBluetoothTransport, createWebSerialTransport, createWebUsbTransport };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { chunk } from "@angadie/chittie-transport";
|
|
2
|
+
//#region src/index.ts
|
|
3
|
+
const nav = () => globalThis.navigator;
|
|
4
|
+
function createWebSerialTransport(options = {}) {
|
|
5
|
+
let port = null;
|
|
6
|
+
const baudRate = options.baudRate ?? 9600;
|
|
7
|
+
return {
|
|
8
|
+
async connect() {
|
|
9
|
+
const serial = nav().serial;
|
|
10
|
+
if (!serial) throw new Error("Web Serial is not supported (use Chrome/Edge desktop over HTTPS).");
|
|
11
|
+
port = (await serial.getPorts())[0] ?? await serial.requestPort();
|
|
12
|
+
await port.open({ baudRate });
|
|
13
|
+
},
|
|
14
|
+
async write(data) {
|
|
15
|
+
if (!port?.writable) throw new Error("Printer not connected — call connect() first.");
|
|
16
|
+
const writer = port.writable.getWriter();
|
|
17
|
+
try {
|
|
18
|
+
await writer.write(data);
|
|
19
|
+
} finally {
|
|
20
|
+
writer.releaseLock();
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
async disconnect() {
|
|
24
|
+
await port?.close();
|
|
25
|
+
port = null;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function createWebBluetoothTransport(options) {
|
|
30
|
+
let characteristic = null;
|
|
31
|
+
let device = null;
|
|
32
|
+
const chunkSize = options.chunkSize ?? 180;
|
|
33
|
+
return {
|
|
34
|
+
async connect() {
|
|
35
|
+
const bluetooth = nav().bluetooth;
|
|
36
|
+
if (!bluetooth) throw new Error("Web Bluetooth is not supported in this browser.");
|
|
37
|
+
device = await bluetooth.requestDevice({
|
|
38
|
+
filters: options.namePrefix ? [{ namePrefix: options.namePrefix }] : void 0,
|
|
39
|
+
optionalServices: [options.serviceUuid],
|
|
40
|
+
acceptAllDevices: options.namePrefix ? void 0 : true
|
|
41
|
+
});
|
|
42
|
+
characteristic = await (await (await device.gatt.connect()).getPrimaryService(options.serviceUuid)).getCharacteristic(options.characteristicUuid);
|
|
43
|
+
},
|
|
44
|
+
async write(data) {
|
|
45
|
+
if (!characteristic) throw new Error("Printer not connected — call connect() first.");
|
|
46
|
+
for (const part of chunk(data, chunkSize)) if (characteristic.writeValueWithoutResponse) await characteristic.writeValueWithoutResponse(part);
|
|
47
|
+
else await characteristic.writeValue(part);
|
|
48
|
+
},
|
|
49
|
+
async disconnect() {
|
|
50
|
+
device?.gatt?.disconnect();
|
|
51
|
+
characteristic = null;
|
|
52
|
+
device = null;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function createWebUsbTransport(options = {}) {
|
|
57
|
+
let device = null;
|
|
58
|
+
let endpoint = 1;
|
|
59
|
+
let iface = 0;
|
|
60
|
+
return {
|
|
61
|
+
async connect() {
|
|
62
|
+
const usb = nav().usb;
|
|
63
|
+
if (!usb) throw new Error("WebUSB is not supported in this browser.");
|
|
64
|
+
device = await usb.requestDevice({ filters: options.filters ?? [] });
|
|
65
|
+
await device.open();
|
|
66
|
+
if (device.configuration === null) await device.selectConfiguration(1);
|
|
67
|
+
const intf = device.configuration.interfaces[0];
|
|
68
|
+
iface = intf.interfaceNumber;
|
|
69
|
+
await device.claimInterface(iface);
|
|
70
|
+
const out = intf.alternate.endpoints.find((e) => e.direction === "out");
|
|
71
|
+
if (!out) throw new Error("No USB OUT endpoint found on this device.");
|
|
72
|
+
endpoint = out.endpointNumber;
|
|
73
|
+
},
|
|
74
|
+
async write(data) {
|
|
75
|
+
if (!device) throw new Error("Printer not connected — call connect() first.");
|
|
76
|
+
await device.transferOut(endpoint, data);
|
|
77
|
+
},
|
|
78
|
+
async disconnect() {
|
|
79
|
+
await device?.close();
|
|
80
|
+
device = null;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Transport that POSTs ESC/POS bytes to the chittie print-agent (`tools/print-agent`)
|
|
86
|
+
* — the way a browser reaches a USB printer-class device (or any OS queue) that it
|
|
87
|
+
* can't open directly. `connect()` pings `/health`; `write()` POSTs to `/print`.
|
|
88
|
+
*/
|
|
89
|
+
function createBridgeTransport(options = {}) {
|
|
90
|
+
const base = (options.url ?? "http://localhost:8930").replace(/\/+$/, "");
|
|
91
|
+
const headers = { "content-type": "application/json" };
|
|
92
|
+
if (options.token) headers["x-agent-token"] = options.token;
|
|
93
|
+
return {
|
|
94
|
+
async connect() {
|
|
95
|
+
const res = await fetch(`${base}/health`);
|
|
96
|
+
if (!res.ok) throw new Error(`chittie: print agent not reachable at ${base} (HTTP ${res.status}).`);
|
|
97
|
+
},
|
|
98
|
+
async write(data) {
|
|
99
|
+
const body = JSON.stringify({
|
|
100
|
+
bytes: Array.from(data),
|
|
101
|
+
...options.printer ? { printer: options.printer } : {}
|
|
102
|
+
});
|
|
103
|
+
const res = await fetch(`${base}/print`, {
|
|
104
|
+
method: "POST",
|
|
105
|
+
headers,
|
|
106
|
+
body
|
|
107
|
+
});
|
|
108
|
+
if (!res.ok) throw new Error(`chittie: print agent /print failed (HTTP ${res.status}).`);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
//#endregion
|
|
113
|
+
export { createBridgeTransport, createWebBluetoothTransport, createWebSerialTransport, createWebUsbTransport };
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@angadie/chittie-transport-web",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Web transports for chittie: Web Serial, WebUSB, Web Bluetooth. Browser platform APIs only.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"types": "./dist/index.d.mts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.mts",
|
|
11
|
+
"import": "./dist/index.mjs"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@angadie/chittie-transport": "0.1.0"
|
|
19
|
+
},
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsdown",
|
|
25
|
+
"typecheck": "tsc --noEmit",
|
|
26
|
+
"test": "tsx spikes/web-serial.spike.ts"
|
|
27
|
+
}
|
|
28
|
+
}
|