@happenv/print-agent-sdk 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/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # @happenv/print-agent-sdk
2
+
3
+ A simple SDK for the local print agent (WebSocket): connect, status, printer
4
+ list, version, and PDF printing. No runtime dependencies.
5
+
6
+ ## Installation
7
+
8
+ ```bash
9
+ npm install @happenv/print-agent-sdk
10
+ ```
11
+
12
+ ## Usage
13
+
14
+ ```ts
15
+ import { PrintAgent } from "@happenv/print-agent-sdk";
16
+
17
+ const agent = new PrintAgent({ port: 45123 }); // wss://localhost:45123/ws
18
+
19
+ agent.onStatusChange((s) => console.log("status:", s));
20
+
21
+ await agent.connect();
22
+
23
+ const printers = await agent.getPrinters();
24
+ console.log(printers); // [{ name, default }]
25
+
26
+ await agent.print({
27
+ printer: printers[0].name,
28
+ url: "https://example.com/document.pdf", // or base64: "<...>"
29
+ copies: 1,
30
+ });
31
+
32
+ agent.disconnect();
33
+ ```
34
+
35
+ ## API
36
+
37
+ - `new PrintAgent(options)` — `{ host?, port?, url?, autoReconnect?, requestTimeoutMs?, WebSocket? }`
38
+ - `connect()` / `disconnect()`
39
+ - `status` / `isConnected()` / `onStatusChange(cb)` — status: `connecting | open | closed`
40
+ - `getPrinters()` → `{ name, default }[]`
41
+ - `getVersion()` → `{ brand, version }`
42
+ - `print({ printer, url? | base64?, copies? })` — resolves once the agent confirms;
43
+ rejects with `AgentError` on an agent-side error
44
+
45
+ Errors: `NotConnectedError`, `TimeoutError`, `AgentError`.
46
+
47
+ ## Notes
48
+
49
+ - The SDK connects over `wss` using a certificate signed by the product's local
50
+ Root CA — it only works from pages on the agent's **origin allowlist** and on a
51
+ machine where the **Root CA is trusted** (installed by the agent's installer).
52
+ - **Print cancellation is not supported**: once a job is handed to the system
53
+ spooler it cannot be reliably revoked.
54
+ - In Node, pass a WebSocket implementation: `new PrintAgent({ WebSocket })`.
55
+ - **Auto-reconnect** is enabled by default. If `connect()` rejects (e.g. on
56
+ timeout), the SDK still keeps retrying in the background with backoff — call
57
+ `disconnect()` to stop it.
package/dist/index.cjs ADDED
@@ -0,0 +1,244 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AgentError: () => AgentError,
24
+ NotConnectedError: () => NotConnectedError,
25
+ PrintAgent: () => PrintAgent,
26
+ TimeoutError: () => TimeoutError
27
+ });
28
+ module.exports = __toCommonJS(index_exports);
29
+
30
+ // src/types.ts
31
+ var NotConnectedError = class extends Error {
32
+ constructor(message = "brak po\u0142\u0105czenia z agentem") {
33
+ super(message);
34
+ this.name = "NotConnectedError";
35
+ }
36
+ };
37
+ var TimeoutError = class extends Error {
38
+ constructor(message = "przekroczono czas oczekiwania na odpowied\u017A agenta") {
39
+ super(message);
40
+ this.name = "TimeoutError";
41
+ }
42
+ };
43
+ var AgentError = class extends Error {
44
+ constructor(message = "b\u0142\u0105d agenta") {
45
+ super(message);
46
+ this.name = "AgentError";
47
+ }
48
+ };
49
+
50
+ // src/client.ts
51
+ var RECONNECT_BACKOFF_MS = [1e3, 2e3, 5e3, 1e4];
52
+ var PrintAgent = class {
53
+ constructor(opts = {}) {
54
+ this._status = "closed";
55
+ this.statusListeners = /* @__PURE__ */ new Set();
56
+ this.pending = /* @__PURE__ */ new Map();
57
+ this.idCounter = 0;
58
+ this.wantConnected = false;
59
+ this.reconnectAttempt = 0;
60
+ const host = opts.host ?? "localhost";
61
+ const port = opts.port ?? 45123;
62
+ this.url = opts.url ?? `wss://${host}:${port}/ws`;
63
+ this.autoReconnect = opts.autoReconnect ?? true;
64
+ this.requestTimeoutMs = opts.requestTimeoutMs ?? 1e4;
65
+ const ctor = opts.WebSocket ?? globalThis.WebSocket;
66
+ if (!ctor) {
67
+ throw new Error("brak implementacji WebSocket (przeka\u017C opcj\u0119 WebSocket)");
68
+ }
69
+ this.WS = ctor;
70
+ }
71
+ get status() {
72
+ return this._status;
73
+ }
74
+ isConnected() {
75
+ return this._status === "open";
76
+ }
77
+ /** Rejestruje nasłuch zmian statusu; zwraca funkcję wyrejestrowującą. */
78
+ onStatusChange(cb) {
79
+ this.statusListeners.add(cb);
80
+ return () => this.statusListeners.delete(cb);
81
+ }
82
+ /** Łączy z agentem; resolve po otwarciu, reject przy timeoucie/zerwaniu.
83
+ * Idempotentne, gdy połączenie jest już otwarte. */
84
+ connect() {
85
+ this.wantConnected = true;
86
+ if (this._status === "open") {
87
+ return Promise.resolve();
88
+ }
89
+ return new Promise((resolve, reject) => {
90
+ this.openSocket(resolve, reject);
91
+ });
92
+ }
93
+ /** Zamyka połączenie i wyłącza auto-reconnect. */
94
+ disconnect() {
95
+ this.wantConnected = false;
96
+ if (this.reconnectTimer) {
97
+ clearTimeout(this.reconnectTimer);
98
+ this.reconnectTimer = void 0;
99
+ }
100
+ if (this.ws) {
101
+ this.ws.close();
102
+ }
103
+ }
104
+ /** Lista drukarek zainstalowanych w systemie klienta. */
105
+ async getPrinters() {
106
+ const r = await this.request({ type: "listPrinters" });
107
+ return r.printers ?? [];
108
+ }
109
+ /** Wersja i brand agenta. */
110
+ async getVersion() {
111
+ const r = await this.request({ type: "version" });
112
+ return { brand: r.brand ?? "", version: r.version ?? "" };
113
+ }
114
+ /** Wysyła zadanie druku PDF; resolve po potwierdzeniu agenta. */
115
+ async print(opts) {
116
+ if (!opts || !opts.printer) {
117
+ throw new Error("print: wymagana nazwa drukarki (printer)");
118
+ }
119
+ const hasUrl = typeof opts.url === "string" && opts.url.length > 0;
120
+ const hasB64 = typeof opts.base64 === "string" && opts.base64.length > 0;
121
+ if (hasUrl === hasB64) {
122
+ throw new Error("print: podaj dok\u0142adnie jedno \u017Ar\xF3d\u0142o \u2014 url albo base64");
123
+ }
124
+ const pdf = hasUrl ? { url: opts.url } : { base64: opts.base64 };
125
+ await this.request({
126
+ type: "printPDF",
127
+ printer: opts.printer,
128
+ pdf,
129
+ copies: opts.copies
130
+ });
131
+ }
132
+ // --- wewnętrzne ---
133
+ openSocket(onOpen, onFail) {
134
+ this.setStatus("connecting");
135
+ let settled = false;
136
+ const ws = new this.WS(this.url);
137
+ this.ws = ws;
138
+ const connectTimer = setTimeout(() => {
139
+ if (!settled) {
140
+ settled = true;
141
+ try {
142
+ ws.close();
143
+ } catch {
144
+ }
145
+ onFail?.(new TimeoutError("przekroczono czas \u0142\u0105czenia z agentem"));
146
+ }
147
+ }, this.requestTimeoutMs);
148
+ ws.onopen = () => {
149
+ settled = true;
150
+ clearTimeout(connectTimer);
151
+ this.reconnectAttempt = 0;
152
+ this.setStatus("open");
153
+ onOpen?.();
154
+ };
155
+ ws.onmessage = (ev) => this.handleMessage(ev.data);
156
+ ws.onerror = () => {
157
+ };
158
+ ws.onclose = () => {
159
+ clearTimeout(connectTimer);
160
+ this.ws = void 0;
161
+ this.setStatus("closed");
162
+ this.rejectAllPending(new NotConnectedError("po\u0142\u0105czenie zamkni\u0119te"));
163
+ if (!settled) {
164
+ settled = true;
165
+ onFail?.(new NotConnectedError("nie uda\u0142o si\u0119 po\u0142\u0105czy\u0107 z agentem"));
166
+ }
167
+ if (this.wantConnected && this.autoReconnect) {
168
+ this.scheduleReconnect();
169
+ }
170
+ };
171
+ }
172
+ scheduleReconnect() {
173
+ const delay = RECONNECT_BACKOFF_MS[Math.min(this.reconnectAttempt, RECONNECT_BACKOFF_MS.length - 1)];
174
+ this.reconnectAttempt++;
175
+ this.reconnectTimer = setTimeout(() => {
176
+ if (this.wantConnected) {
177
+ this.openSocket();
178
+ }
179
+ }, delay);
180
+ }
181
+ handleMessage(data) {
182
+ const text = typeof data === "string" ? data : String(data);
183
+ let msg;
184
+ try {
185
+ msg = JSON.parse(text);
186
+ } catch {
187
+ return;
188
+ }
189
+ const id = msg.id;
190
+ if (!id) return;
191
+ const p = this.pending.get(id);
192
+ if (!p) return;
193
+ clearTimeout(p.timer);
194
+ this.pending.delete(id);
195
+ if (msg.ok) {
196
+ p.resolve(msg);
197
+ } else {
198
+ p.reject(new AgentError(msg.error || "b\u0142\u0105d agenta"));
199
+ }
200
+ }
201
+ request(payload) {
202
+ if (!this.isConnected() || !this.ws) {
203
+ return Promise.reject(new NotConnectedError());
204
+ }
205
+ const id = String(++this.idCounter);
206
+ const ws = this.ws;
207
+ return new Promise((resolve, reject) => {
208
+ const timer = setTimeout(() => {
209
+ this.pending.delete(id);
210
+ reject(new TimeoutError());
211
+ }, this.requestTimeoutMs);
212
+ this.pending.set(id, { resolve, reject, timer });
213
+ try {
214
+ ws.send(JSON.stringify({ ...payload, id }));
215
+ } catch (e) {
216
+ clearTimeout(timer);
217
+ this.pending.delete(id);
218
+ reject(e instanceof Error ? e : new Error(String(e)));
219
+ }
220
+ });
221
+ }
222
+ rejectAllPending(err) {
223
+ for (const [, p] of this.pending) {
224
+ clearTimeout(p.timer);
225
+ p.reject(err);
226
+ }
227
+ this.pending.clear();
228
+ }
229
+ setStatus(s) {
230
+ if (this._status === s) return;
231
+ this._status = s;
232
+ for (const cb of this.statusListeners) {
233
+ cb(s);
234
+ }
235
+ }
236
+ };
237
+ // Annotate the CommonJS export names for ESM import in node:
238
+ 0 && (module.exports = {
239
+ AgentError,
240
+ NotConnectedError,
241
+ PrintAgent,
242
+ TimeoutError
243
+ });
244
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/types.ts","../src/client.ts"],"sourcesContent":["export { PrintAgent } from \"./client\";\nexport {\n AgentError,\n NotConnectedError,\n TimeoutError,\n type AgentVersion,\n type ConnectionStatus,\n type Printer,\n type PrintAgentOptions,\n type PrintOptions,\n type WebSocketCtor,\n type WebSocketLike,\n} from \"./types\";\n","/** Status połączenia z lokalnym agentem. */\nexport type ConnectionStatus = \"connecting\" | \"open\" | \"closed\";\n\n/** Drukarka zwrócona przez agenta. */\nexport interface Printer {\n name: string;\n default: boolean;\n}\n\n/** Wersja agenta. */\nexport interface AgentVersion {\n brand: string;\n version: string;\n}\n\n/** Parametry zadania druku. Dokładnie jedno ze źródeł: url albo base64. */\nexport interface PrintOptions {\n printer: string;\n url?: string;\n base64?: string;\n copies?: number;\n}\n\n/** Opcje konstruktora PrintAgent. */\nexport interface PrintAgentOptions {\n host?: string;\n port?: number;\n url?: string;\n autoReconnect?: boolean;\n requestTimeoutMs?: number;\n WebSocket?: WebSocketCtor;\n}\n\n/** Minimalny interfejs WebSocket używany przez SDK (zgodny z przeglądarką i pakietem ws). */\nexport interface WebSocketLike {\n send(data: string): void;\n close(): void;\n onopen: ((ev: unknown) => void) | null;\n onmessage: ((ev: { data: unknown }) => void) | null;\n onclose: ((ev: unknown) => void) | null;\n onerror: ((ev: unknown) => void) | null;\n}\n\nexport interface WebSocketCtor {\n new (url: string): WebSocketLike;\n}\n\n/** Brak aktywnego połączenia z agentem. */\nexport class NotConnectedError extends Error {\n constructor(message = \"brak połączenia z agentem\") {\n super(message);\n this.name = \"NotConnectedError\";\n }\n}\n\n/** Przekroczono czas oczekiwania na odpowiedź agenta. */\nexport class TimeoutError extends Error {\n constructor(message = \"przekroczono czas oczekiwania na odpowiedź agenta\") {\n super(message);\n this.name = \"TimeoutError\";\n }\n}\n\n/** Agent odpowiedział błędem (ok:false). */\nexport class AgentError extends Error {\n constructor(message = \"błąd agenta\") {\n super(message);\n this.name = \"AgentError\";\n }\n}\n","import {\n AgentError,\n NotConnectedError,\n TimeoutError,\n type AgentVersion,\n type ConnectionStatus,\n type PrintAgentOptions,\n type PrintOptions,\n type Printer,\n type WebSocketCtor,\n type WebSocketLike,\n} from \"./types\";\n\ninterface AgentResponse {\n type: string;\n id?: string;\n ok: boolean;\n error?: string;\n printers?: Printer[];\n brand?: string;\n version?: string;\n}\n\ninterface Pending {\n resolve: (r: AgentResponse) => void;\n reject: (e: Error) => void;\n timer: ReturnType<typeof setTimeout>;\n}\n\nconst RECONNECT_BACKOFF_MS = [1000, 2000, 5000, 10000];\n\n/** Klient lokalnego agenta druku. */\nexport class PrintAgent {\n private readonly url: string;\n private readonly autoReconnect: boolean;\n private readonly requestTimeoutMs: number;\n private readonly WS: WebSocketCtor;\n\n private ws?: WebSocketLike;\n private _status: ConnectionStatus = \"closed\";\n private statusListeners = new Set<(s: ConnectionStatus) => void>();\n private pending = new Map<string, Pending>();\n private idCounter = 0;\n private wantConnected = false;\n private reconnectAttempt = 0;\n private reconnectTimer?: ReturnType<typeof setTimeout>;\n\n constructor(opts: PrintAgentOptions = {}) {\n const host = opts.host ?? \"localhost\";\n const port = opts.port ?? 45123;\n this.url = opts.url ?? `wss://${host}:${port}/ws`;\n this.autoReconnect = opts.autoReconnect ?? true;\n this.requestTimeoutMs = opts.requestTimeoutMs ?? 10000;\n const ctor = opts.WebSocket ?? (globalThis as { WebSocket?: WebSocketCtor }).WebSocket;\n if (!ctor) {\n throw new Error(\"brak implementacji WebSocket (przekaż opcję WebSocket)\");\n }\n this.WS = ctor;\n }\n\n get status(): ConnectionStatus {\n return this._status;\n }\n\n isConnected(): boolean {\n return this._status === \"open\";\n }\n\n /** Rejestruje nasłuch zmian statusu; zwraca funkcję wyrejestrowującą. */\n onStatusChange(cb: (s: ConnectionStatus) => void): () => void {\n this.statusListeners.add(cb);\n return () => this.statusListeners.delete(cb);\n }\n\n /** Łączy z agentem; resolve po otwarciu, reject przy timeoucie/zerwaniu.\n * Idempotentne, gdy połączenie jest już otwarte. */\n connect(): Promise<void> {\n this.wantConnected = true;\n if (this._status === \"open\") {\n return Promise.resolve();\n }\n return new Promise<void>((resolve, reject) => {\n this.openSocket(resolve, reject);\n });\n }\n\n /** Zamyka połączenie i wyłącza auto-reconnect. */\n disconnect(): void {\n this.wantConnected = false;\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = undefined;\n }\n if (this.ws) {\n this.ws.close();\n }\n }\n\n /** Lista drukarek zainstalowanych w systemie klienta. */\n async getPrinters(): Promise<Printer[]> {\n const r = await this.request({ type: \"listPrinters\" });\n return r.printers ?? [];\n }\n\n /** Wersja i brand agenta. */\n async getVersion(): Promise<AgentVersion> {\n const r = await this.request({ type: \"version\" });\n return { brand: r.brand ?? \"\", version: r.version ?? \"\" };\n }\n\n /** Wysyła zadanie druku PDF; resolve po potwierdzeniu agenta. */\n async print(opts: PrintOptions): Promise<void> {\n if (!opts || !opts.printer) {\n throw new Error(\"print: wymagana nazwa drukarki (printer)\");\n }\n const hasUrl = typeof opts.url === \"string\" && opts.url.length > 0;\n const hasB64 = typeof opts.base64 === \"string\" && opts.base64.length > 0;\n if (hasUrl === hasB64) {\n throw new Error(\"print: podaj dokładnie jedno źródło — url albo base64\");\n }\n const pdf = hasUrl ? { url: opts.url } : { base64: opts.base64 };\n await this.request({\n type: \"printPDF\",\n printer: opts.printer,\n pdf,\n copies: opts.copies,\n });\n }\n\n // --- wewnętrzne ---\n\n private openSocket(\n onOpen?: () => void,\n onFail?: (e: Error) => void\n ): void {\n this.setStatus(\"connecting\");\n let settled = false;\n const ws = new this.WS(this.url);\n this.ws = ws;\n\n const connectTimer = setTimeout(() => {\n if (!settled) {\n settled = true;\n try {\n ws.close();\n } catch {\n /* ignore */\n }\n onFail?.(new TimeoutError(\"przekroczono czas łączenia z agentem\"));\n }\n }, this.requestTimeoutMs);\n\n ws.onopen = () => {\n settled = true;\n clearTimeout(connectTimer);\n this.reconnectAttempt = 0;\n this.setStatus(\"open\");\n onOpen?.();\n };\n ws.onmessage = (ev) => this.handleMessage(ev.data);\n ws.onerror = () => {\n /* po błędzie nastąpi onclose */\n };\n ws.onclose = () => {\n clearTimeout(connectTimer);\n this.ws = undefined;\n this.setStatus(\"closed\");\n this.rejectAllPending(new NotConnectedError(\"połączenie zamknięte\"));\n if (!settled) {\n settled = true;\n onFail?.(new NotConnectedError(\"nie udało się połączyć z agentem\"));\n }\n if (this.wantConnected && this.autoReconnect) {\n this.scheduleReconnect();\n }\n };\n }\n\n private scheduleReconnect(): void {\n const delay =\n RECONNECT_BACKOFF_MS[Math.min(this.reconnectAttempt, RECONNECT_BACKOFF_MS.length - 1)];\n this.reconnectAttempt++;\n this.reconnectTimer = setTimeout(() => {\n if (this.wantConnected) {\n this.openSocket();\n }\n }, delay);\n }\n\n private handleMessage(data: unknown): void {\n const text = typeof data === \"string\" ? data : String(data);\n let msg: AgentResponse;\n try {\n msg = JSON.parse(text);\n } catch {\n return;\n }\n const id = msg.id;\n if (!id) return;\n const p = this.pending.get(id);\n if (!p) return;\n clearTimeout(p.timer);\n this.pending.delete(id);\n if (msg.ok) {\n p.resolve(msg);\n } else {\n p.reject(new AgentError(msg.error || \"błąd agenta\"));\n }\n }\n\n private request(payload: Record<string, unknown>): Promise<AgentResponse> {\n if (!this.isConnected() || !this.ws) {\n return Promise.reject(new NotConnectedError());\n }\n const id = String(++this.idCounter);\n const ws = this.ws;\n return new Promise<AgentResponse>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.pending.delete(id);\n reject(new TimeoutError());\n }, this.requestTimeoutMs);\n this.pending.set(id, { resolve, reject, timer });\n try {\n ws.send(JSON.stringify({ ...payload, id }));\n } catch (e) {\n clearTimeout(timer);\n this.pending.delete(id);\n reject(e instanceof Error ? e : new Error(String(e)));\n }\n });\n }\n\n private rejectAllPending(err: Error): void {\n for (const [, p] of this.pending) {\n clearTimeout(p.timer);\n p.reject(err);\n }\n this.pending.clear();\n }\n\n private setStatus(s: ConnectionStatus): void {\n if (this._status === s) return;\n this._status = s;\n for (const cb of this.statusListeners) {\n cb(s);\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,94 @@
1
+ /** Status połączenia z lokalnym agentem. */
2
+ type ConnectionStatus = "connecting" | "open" | "closed";
3
+ /** Drukarka zwrócona przez agenta. */
4
+ interface Printer {
5
+ name: string;
6
+ default: boolean;
7
+ }
8
+ /** Wersja agenta. */
9
+ interface AgentVersion {
10
+ brand: string;
11
+ version: string;
12
+ }
13
+ /** Parametry zadania druku. Dokładnie jedno ze źródeł: url albo base64. */
14
+ interface PrintOptions {
15
+ printer: string;
16
+ url?: string;
17
+ base64?: string;
18
+ copies?: number;
19
+ }
20
+ /** Opcje konstruktora PrintAgent. */
21
+ interface PrintAgentOptions {
22
+ host?: string;
23
+ port?: number;
24
+ url?: string;
25
+ autoReconnect?: boolean;
26
+ requestTimeoutMs?: number;
27
+ WebSocket?: WebSocketCtor;
28
+ }
29
+ /** Minimalny interfejs WebSocket używany przez SDK (zgodny z przeglądarką i pakietem ws). */
30
+ interface WebSocketLike {
31
+ send(data: string): void;
32
+ close(): void;
33
+ onopen: ((ev: unknown) => void) | null;
34
+ onmessage: ((ev: {
35
+ data: unknown;
36
+ }) => void) | null;
37
+ onclose: ((ev: unknown) => void) | null;
38
+ onerror: ((ev: unknown) => void) | null;
39
+ }
40
+ interface WebSocketCtor {
41
+ new (url: string): WebSocketLike;
42
+ }
43
+ /** Brak aktywnego połączenia z agentem. */
44
+ declare class NotConnectedError extends Error {
45
+ constructor(message?: string);
46
+ }
47
+ /** Przekroczono czas oczekiwania na odpowiedź agenta. */
48
+ declare class TimeoutError extends Error {
49
+ constructor(message?: string);
50
+ }
51
+ /** Agent odpowiedział błędem (ok:false). */
52
+ declare class AgentError extends Error {
53
+ constructor(message?: string);
54
+ }
55
+
56
+ /** Klient lokalnego agenta druku. */
57
+ declare class PrintAgent {
58
+ private readonly url;
59
+ private readonly autoReconnect;
60
+ private readonly requestTimeoutMs;
61
+ private readonly WS;
62
+ private ws?;
63
+ private _status;
64
+ private statusListeners;
65
+ private pending;
66
+ private idCounter;
67
+ private wantConnected;
68
+ private reconnectAttempt;
69
+ private reconnectTimer?;
70
+ constructor(opts?: PrintAgentOptions);
71
+ get status(): ConnectionStatus;
72
+ isConnected(): boolean;
73
+ /** Rejestruje nasłuch zmian statusu; zwraca funkcję wyrejestrowującą. */
74
+ onStatusChange(cb: (s: ConnectionStatus) => void): () => void;
75
+ /** Łączy z agentem; resolve po otwarciu, reject przy timeoucie/zerwaniu.
76
+ * Idempotentne, gdy połączenie jest już otwarte. */
77
+ connect(): Promise<void>;
78
+ /** Zamyka połączenie i wyłącza auto-reconnect. */
79
+ disconnect(): void;
80
+ /** Lista drukarek zainstalowanych w systemie klienta. */
81
+ getPrinters(): Promise<Printer[]>;
82
+ /** Wersja i brand agenta. */
83
+ getVersion(): Promise<AgentVersion>;
84
+ /** Wysyła zadanie druku PDF; resolve po potwierdzeniu agenta. */
85
+ print(opts: PrintOptions): Promise<void>;
86
+ private openSocket;
87
+ private scheduleReconnect;
88
+ private handleMessage;
89
+ private request;
90
+ private rejectAllPending;
91
+ private setStatus;
92
+ }
93
+
94
+ export { AgentError, type AgentVersion, type ConnectionStatus, NotConnectedError, PrintAgent, type PrintAgentOptions, type PrintOptions, type Printer, TimeoutError, type WebSocketCtor, type WebSocketLike };
@@ -0,0 +1,94 @@
1
+ /** Status połączenia z lokalnym agentem. */
2
+ type ConnectionStatus = "connecting" | "open" | "closed";
3
+ /** Drukarka zwrócona przez agenta. */
4
+ interface Printer {
5
+ name: string;
6
+ default: boolean;
7
+ }
8
+ /** Wersja agenta. */
9
+ interface AgentVersion {
10
+ brand: string;
11
+ version: string;
12
+ }
13
+ /** Parametry zadania druku. Dokładnie jedno ze źródeł: url albo base64. */
14
+ interface PrintOptions {
15
+ printer: string;
16
+ url?: string;
17
+ base64?: string;
18
+ copies?: number;
19
+ }
20
+ /** Opcje konstruktora PrintAgent. */
21
+ interface PrintAgentOptions {
22
+ host?: string;
23
+ port?: number;
24
+ url?: string;
25
+ autoReconnect?: boolean;
26
+ requestTimeoutMs?: number;
27
+ WebSocket?: WebSocketCtor;
28
+ }
29
+ /** Minimalny interfejs WebSocket używany przez SDK (zgodny z przeglądarką i pakietem ws). */
30
+ interface WebSocketLike {
31
+ send(data: string): void;
32
+ close(): void;
33
+ onopen: ((ev: unknown) => void) | null;
34
+ onmessage: ((ev: {
35
+ data: unknown;
36
+ }) => void) | null;
37
+ onclose: ((ev: unknown) => void) | null;
38
+ onerror: ((ev: unknown) => void) | null;
39
+ }
40
+ interface WebSocketCtor {
41
+ new (url: string): WebSocketLike;
42
+ }
43
+ /** Brak aktywnego połączenia z agentem. */
44
+ declare class NotConnectedError extends Error {
45
+ constructor(message?: string);
46
+ }
47
+ /** Przekroczono czas oczekiwania na odpowiedź agenta. */
48
+ declare class TimeoutError extends Error {
49
+ constructor(message?: string);
50
+ }
51
+ /** Agent odpowiedział błędem (ok:false). */
52
+ declare class AgentError extends Error {
53
+ constructor(message?: string);
54
+ }
55
+
56
+ /** Klient lokalnego agenta druku. */
57
+ declare class PrintAgent {
58
+ private readonly url;
59
+ private readonly autoReconnect;
60
+ private readonly requestTimeoutMs;
61
+ private readonly WS;
62
+ private ws?;
63
+ private _status;
64
+ private statusListeners;
65
+ private pending;
66
+ private idCounter;
67
+ private wantConnected;
68
+ private reconnectAttempt;
69
+ private reconnectTimer?;
70
+ constructor(opts?: PrintAgentOptions);
71
+ get status(): ConnectionStatus;
72
+ isConnected(): boolean;
73
+ /** Rejestruje nasłuch zmian statusu; zwraca funkcję wyrejestrowującą. */
74
+ onStatusChange(cb: (s: ConnectionStatus) => void): () => void;
75
+ /** Łączy z agentem; resolve po otwarciu, reject przy timeoucie/zerwaniu.
76
+ * Idempotentne, gdy połączenie jest już otwarte. */
77
+ connect(): Promise<void>;
78
+ /** Zamyka połączenie i wyłącza auto-reconnect. */
79
+ disconnect(): void;
80
+ /** Lista drukarek zainstalowanych w systemie klienta. */
81
+ getPrinters(): Promise<Printer[]>;
82
+ /** Wersja i brand agenta. */
83
+ getVersion(): Promise<AgentVersion>;
84
+ /** Wysyła zadanie druku PDF; resolve po potwierdzeniu agenta. */
85
+ print(opts: PrintOptions): Promise<void>;
86
+ private openSocket;
87
+ private scheduleReconnect;
88
+ private handleMessage;
89
+ private request;
90
+ private rejectAllPending;
91
+ private setStatus;
92
+ }
93
+
94
+ export { AgentError, type AgentVersion, type ConnectionStatus, NotConnectedError, PrintAgent, type PrintAgentOptions, type PrintOptions, type Printer, TimeoutError, type WebSocketCtor, type WebSocketLike };
package/dist/index.js ADDED
@@ -0,0 +1,214 @@
1
+ // src/types.ts
2
+ var NotConnectedError = class extends Error {
3
+ constructor(message = "brak po\u0142\u0105czenia z agentem") {
4
+ super(message);
5
+ this.name = "NotConnectedError";
6
+ }
7
+ };
8
+ var TimeoutError = class extends Error {
9
+ constructor(message = "przekroczono czas oczekiwania na odpowied\u017A agenta") {
10
+ super(message);
11
+ this.name = "TimeoutError";
12
+ }
13
+ };
14
+ var AgentError = class extends Error {
15
+ constructor(message = "b\u0142\u0105d agenta") {
16
+ super(message);
17
+ this.name = "AgentError";
18
+ }
19
+ };
20
+
21
+ // src/client.ts
22
+ var RECONNECT_BACKOFF_MS = [1e3, 2e3, 5e3, 1e4];
23
+ var PrintAgent = class {
24
+ constructor(opts = {}) {
25
+ this._status = "closed";
26
+ this.statusListeners = /* @__PURE__ */ new Set();
27
+ this.pending = /* @__PURE__ */ new Map();
28
+ this.idCounter = 0;
29
+ this.wantConnected = false;
30
+ this.reconnectAttempt = 0;
31
+ const host = opts.host ?? "localhost";
32
+ const port = opts.port ?? 45123;
33
+ this.url = opts.url ?? `wss://${host}:${port}/ws`;
34
+ this.autoReconnect = opts.autoReconnect ?? true;
35
+ this.requestTimeoutMs = opts.requestTimeoutMs ?? 1e4;
36
+ const ctor = opts.WebSocket ?? globalThis.WebSocket;
37
+ if (!ctor) {
38
+ throw new Error("brak implementacji WebSocket (przeka\u017C opcj\u0119 WebSocket)");
39
+ }
40
+ this.WS = ctor;
41
+ }
42
+ get status() {
43
+ return this._status;
44
+ }
45
+ isConnected() {
46
+ return this._status === "open";
47
+ }
48
+ /** Rejestruje nasłuch zmian statusu; zwraca funkcję wyrejestrowującą. */
49
+ onStatusChange(cb) {
50
+ this.statusListeners.add(cb);
51
+ return () => this.statusListeners.delete(cb);
52
+ }
53
+ /** Łączy z agentem; resolve po otwarciu, reject przy timeoucie/zerwaniu.
54
+ * Idempotentne, gdy połączenie jest już otwarte. */
55
+ connect() {
56
+ this.wantConnected = true;
57
+ if (this._status === "open") {
58
+ return Promise.resolve();
59
+ }
60
+ return new Promise((resolve, reject) => {
61
+ this.openSocket(resolve, reject);
62
+ });
63
+ }
64
+ /** Zamyka połączenie i wyłącza auto-reconnect. */
65
+ disconnect() {
66
+ this.wantConnected = false;
67
+ if (this.reconnectTimer) {
68
+ clearTimeout(this.reconnectTimer);
69
+ this.reconnectTimer = void 0;
70
+ }
71
+ if (this.ws) {
72
+ this.ws.close();
73
+ }
74
+ }
75
+ /** Lista drukarek zainstalowanych w systemie klienta. */
76
+ async getPrinters() {
77
+ const r = await this.request({ type: "listPrinters" });
78
+ return r.printers ?? [];
79
+ }
80
+ /** Wersja i brand agenta. */
81
+ async getVersion() {
82
+ const r = await this.request({ type: "version" });
83
+ return { brand: r.brand ?? "", version: r.version ?? "" };
84
+ }
85
+ /** Wysyła zadanie druku PDF; resolve po potwierdzeniu agenta. */
86
+ async print(opts) {
87
+ if (!opts || !opts.printer) {
88
+ throw new Error("print: wymagana nazwa drukarki (printer)");
89
+ }
90
+ const hasUrl = typeof opts.url === "string" && opts.url.length > 0;
91
+ const hasB64 = typeof opts.base64 === "string" && opts.base64.length > 0;
92
+ if (hasUrl === hasB64) {
93
+ throw new Error("print: podaj dok\u0142adnie jedno \u017Ar\xF3d\u0142o \u2014 url albo base64");
94
+ }
95
+ const pdf = hasUrl ? { url: opts.url } : { base64: opts.base64 };
96
+ await this.request({
97
+ type: "printPDF",
98
+ printer: opts.printer,
99
+ pdf,
100
+ copies: opts.copies
101
+ });
102
+ }
103
+ // --- wewnętrzne ---
104
+ openSocket(onOpen, onFail) {
105
+ this.setStatus("connecting");
106
+ let settled = false;
107
+ const ws = new this.WS(this.url);
108
+ this.ws = ws;
109
+ const connectTimer = setTimeout(() => {
110
+ if (!settled) {
111
+ settled = true;
112
+ try {
113
+ ws.close();
114
+ } catch {
115
+ }
116
+ onFail?.(new TimeoutError("przekroczono czas \u0142\u0105czenia z agentem"));
117
+ }
118
+ }, this.requestTimeoutMs);
119
+ ws.onopen = () => {
120
+ settled = true;
121
+ clearTimeout(connectTimer);
122
+ this.reconnectAttempt = 0;
123
+ this.setStatus("open");
124
+ onOpen?.();
125
+ };
126
+ ws.onmessage = (ev) => this.handleMessage(ev.data);
127
+ ws.onerror = () => {
128
+ };
129
+ ws.onclose = () => {
130
+ clearTimeout(connectTimer);
131
+ this.ws = void 0;
132
+ this.setStatus("closed");
133
+ this.rejectAllPending(new NotConnectedError("po\u0142\u0105czenie zamkni\u0119te"));
134
+ if (!settled) {
135
+ settled = true;
136
+ onFail?.(new NotConnectedError("nie uda\u0142o si\u0119 po\u0142\u0105czy\u0107 z agentem"));
137
+ }
138
+ if (this.wantConnected && this.autoReconnect) {
139
+ this.scheduleReconnect();
140
+ }
141
+ };
142
+ }
143
+ scheduleReconnect() {
144
+ const delay = RECONNECT_BACKOFF_MS[Math.min(this.reconnectAttempt, RECONNECT_BACKOFF_MS.length - 1)];
145
+ this.reconnectAttempt++;
146
+ this.reconnectTimer = setTimeout(() => {
147
+ if (this.wantConnected) {
148
+ this.openSocket();
149
+ }
150
+ }, delay);
151
+ }
152
+ handleMessage(data) {
153
+ const text = typeof data === "string" ? data : String(data);
154
+ let msg;
155
+ try {
156
+ msg = JSON.parse(text);
157
+ } catch {
158
+ return;
159
+ }
160
+ const id = msg.id;
161
+ if (!id) return;
162
+ const p = this.pending.get(id);
163
+ if (!p) return;
164
+ clearTimeout(p.timer);
165
+ this.pending.delete(id);
166
+ if (msg.ok) {
167
+ p.resolve(msg);
168
+ } else {
169
+ p.reject(new AgentError(msg.error || "b\u0142\u0105d agenta"));
170
+ }
171
+ }
172
+ request(payload) {
173
+ if (!this.isConnected() || !this.ws) {
174
+ return Promise.reject(new NotConnectedError());
175
+ }
176
+ const id = String(++this.idCounter);
177
+ const ws = this.ws;
178
+ return new Promise((resolve, reject) => {
179
+ const timer = setTimeout(() => {
180
+ this.pending.delete(id);
181
+ reject(new TimeoutError());
182
+ }, this.requestTimeoutMs);
183
+ this.pending.set(id, { resolve, reject, timer });
184
+ try {
185
+ ws.send(JSON.stringify({ ...payload, id }));
186
+ } catch (e) {
187
+ clearTimeout(timer);
188
+ this.pending.delete(id);
189
+ reject(e instanceof Error ? e : new Error(String(e)));
190
+ }
191
+ });
192
+ }
193
+ rejectAllPending(err) {
194
+ for (const [, p] of this.pending) {
195
+ clearTimeout(p.timer);
196
+ p.reject(err);
197
+ }
198
+ this.pending.clear();
199
+ }
200
+ setStatus(s) {
201
+ if (this._status === s) return;
202
+ this._status = s;
203
+ for (const cb of this.statusListeners) {
204
+ cb(s);
205
+ }
206
+ }
207
+ };
208
+ export {
209
+ AgentError,
210
+ NotConnectedError,
211
+ PrintAgent,
212
+ TimeoutError
213
+ };
214
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/client.ts"],"sourcesContent":["/** Status połączenia z lokalnym agentem. */\nexport type ConnectionStatus = \"connecting\" | \"open\" | \"closed\";\n\n/** Drukarka zwrócona przez agenta. */\nexport interface Printer {\n name: string;\n default: boolean;\n}\n\n/** Wersja agenta. */\nexport interface AgentVersion {\n brand: string;\n version: string;\n}\n\n/** Parametry zadania druku. Dokładnie jedno ze źródeł: url albo base64. */\nexport interface PrintOptions {\n printer: string;\n url?: string;\n base64?: string;\n copies?: number;\n}\n\n/** Opcje konstruktora PrintAgent. */\nexport interface PrintAgentOptions {\n host?: string;\n port?: number;\n url?: string;\n autoReconnect?: boolean;\n requestTimeoutMs?: number;\n WebSocket?: WebSocketCtor;\n}\n\n/** Minimalny interfejs WebSocket używany przez SDK (zgodny z przeglądarką i pakietem ws). */\nexport interface WebSocketLike {\n send(data: string): void;\n close(): void;\n onopen: ((ev: unknown) => void) | null;\n onmessage: ((ev: { data: unknown }) => void) | null;\n onclose: ((ev: unknown) => void) | null;\n onerror: ((ev: unknown) => void) | null;\n}\n\nexport interface WebSocketCtor {\n new (url: string): WebSocketLike;\n}\n\n/** Brak aktywnego połączenia z agentem. */\nexport class NotConnectedError extends Error {\n constructor(message = \"brak połączenia z agentem\") {\n super(message);\n this.name = \"NotConnectedError\";\n }\n}\n\n/** Przekroczono czas oczekiwania na odpowiedź agenta. */\nexport class TimeoutError extends Error {\n constructor(message = \"przekroczono czas oczekiwania na odpowiedź agenta\") {\n super(message);\n this.name = \"TimeoutError\";\n }\n}\n\n/** Agent odpowiedział błędem (ok:false). */\nexport class AgentError extends Error {\n constructor(message = \"błąd agenta\") {\n super(message);\n this.name = \"AgentError\";\n }\n}\n","import {\n AgentError,\n NotConnectedError,\n TimeoutError,\n type AgentVersion,\n type ConnectionStatus,\n type PrintAgentOptions,\n type PrintOptions,\n type Printer,\n type WebSocketCtor,\n type WebSocketLike,\n} from \"./types\";\n\ninterface AgentResponse {\n type: string;\n id?: string;\n ok: boolean;\n error?: string;\n printers?: Printer[];\n brand?: string;\n version?: string;\n}\n\ninterface Pending {\n resolve: (r: AgentResponse) => void;\n reject: (e: Error) => void;\n timer: ReturnType<typeof setTimeout>;\n}\n\nconst RECONNECT_BACKOFF_MS = [1000, 2000, 5000, 10000];\n\n/** Klient lokalnego agenta druku. */\nexport class PrintAgent {\n private readonly url: string;\n private readonly autoReconnect: boolean;\n private readonly requestTimeoutMs: number;\n private readonly WS: WebSocketCtor;\n\n private ws?: WebSocketLike;\n private _status: ConnectionStatus = \"closed\";\n private statusListeners = new Set<(s: ConnectionStatus) => void>();\n private pending = new Map<string, Pending>();\n private idCounter = 0;\n private wantConnected = false;\n private reconnectAttempt = 0;\n private reconnectTimer?: ReturnType<typeof setTimeout>;\n\n constructor(opts: PrintAgentOptions = {}) {\n const host = opts.host ?? \"localhost\";\n const port = opts.port ?? 45123;\n this.url = opts.url ?? `wss://${host}:${port}/ws`;\n this.autoReconnect = opts.autoReconnect ?? true;\n this.requestTimeoutMs = opts.requestTimeoutMs ?? 10000;\n const ctor = opts.WebSocket ?? (globalThis as { WebSocket?: WebSocketCtor }).WebSocket;\n if (!ctor) {\n throw new Error(\"brak implementacji WebSocket (przekaż opcję WebSocket)\");\n }\n this.WS = ctor;\n }\n\n get status(): ConnectionStatus {\n return this._status;\n }\n\n isConnected(): boolean {\n return this._status === \"open\";\n }\n\n /** Rejestruje nasłuch zmian statusu; zwraca funkcję wyrejestrowującą. */\n onStatusChange(cb: (s: ConnectionStatus) => void): () => void {\n this.statusListeners.add(cb);\n return () => this.statusListeners.delete(cb);\n }\n\n /** Łączy z agentem; resolve po otwarciu, reject przy timeoucie/zerwaniu.\n * Idempotentne, gdy połączenie jest już otwarte. */\n connect(): Promise<void> {\n this.wantConnected = true;\n if (this._status === \"open\") {\n return Promise.resolve();\n }\n return new Promise<void>((resolve, reject) => {\n this.openSocket(resolve, reject);\n });\n }\n\n /** Zamyka połączenie i wyłącza auto-reconnect. */\n disconnect(): void {\n this.wantConnected = false;\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = undefined;\n }\n if (this.ws) {\n this.ws.close();\n }\n }\n\n /** Lista drukarek zainstalowanych w systemie klienta. */\n async getPrinters(): Promise<Printer[]> {\n const r = await this.request({ type: \"listPrinters\" });\n return r.printers ?? [];\n }\n\n /** Wersja i brand agenta. */\n async getVersion(): Promise<AgentVersion> {\n const r = await this.request({ type: \"version\" });\n return { brand: r.brand ?? \"\", version: r.version ?? \"\" };\n }\n\n /** Wysyła zadanie druku PDF; resolve po potwierdzeniu agenta. */\n async print(opts: PrintOptions): Promise<void> {\n if (!opts || !opts.printer) {\n throw new Error(\"print: wymagana nazwa drukarki (printer)\");\n }\n const hasUrl = typeof opts.url === \"string\" && opts.url.length > 0;\n const hasB64 = typeof opts.base64 === \"string\" && opts.base64.length > 0;\n if (hasUrl === hasB64) {\n throw new Error(\"print: podaj dokładnie jedno źródło — url albo base64\");\n }\n const pdf = hasUrl ? { url: opts.url } : { base64: opts.base64 };\n await this.request({\n type: \"printPDF\",\n printer: opts.printer,\n pdf,\n copies: opts.copies,\n });\n }\n\n // --- wewnętrzne ---\n\n private openSocket(\n onOpen?: () => void,\n onFail?: (e: Error) => void\n ): void {\n this.setStatus(\"connecting\");\n let settled = false;\n const ws = new this.WS(this.url);\n this.ws = ws;\n\n const connectTimer = setTimeout(() => {\n if (!settled) {\n settled = true;\n try {\n ws.close();\n } catch {\n /* ignore */\n }\n onFail?.(new TimeoutError(\"przekroczono czas łączenia z agentem\"));\n }\n }, this.requestTimeoutMs);\n\n ws.onopen = () => {\n settled = true;\n clearTimeout(connectTimer);\n this.reconnectAttempt = 0;\n this.setStatus(\"open\");\n onOpen?.();\n };\n ws.onmessage = (ev) => this.handleMessage(ev.data);\n ws.onerror = () => {\n /* po błędzie nastąpi onclose */\n };\n ws.onclose = () => {\n clearTimeout(connectTimer);\n this.ws = undefined;\n this.setStatus(\"closed\");\n this.rejectAllPending(new NotConnectedError(\"połączenie zamknięte\"));\n if (!settled) {\n settled = true;\n onFail?.(new NotConnectedError(\"nie udało się połączyć z agentem\"));\n }\n if (this.wantConnected && this.autoReconnect) {\n this.scheduleReconnect();\n }\n };\n }\n\n private scheduleReconnect(): void {\n const delay =\n RECONNECT_BACKOFF_MS[Math.min(this.reconnectAttempt, RECONNECT_BACKOFF_MS.length - 1)];\n this.reconnectAttempt++;\n this.reconnectTimer = setTimeout(() => {\n if (this.wantConnected) {\n this.openSocket();\n }\n }, delay);\n }\n\n private handleMessage(data: unknown): void {\n const text = typeof data === \"string\" ? data : String(data);\n let msg: AgentResponse;\n try {\n msg = JSON.parse(text);\n } catch {\n return;\n }\n const id = msg.id;\n if (!id) return;\n const p = this.pending.get(id);\n if (!p) return;\n clearTimeout(p.timer);\n this.pending.delete(id);\n if (msg.ok) {\n p.resolve(msg);\n } else {\n p.reject(new AgentError(msg.error || \"błąd agenta\"));\n }\n }\n\n private request(payload: Record<string, unknown>): Promise<AgentResponse> {\n if (!this.isConnected() || !this.ws) {\n return Promise.reject(new NotConnectedError());\n }\n const id = String(++this.idCounter);\n const ws = this.ws;\n return new Promise<AgentResponse>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.pending.delete(id);\n reject(new TimeoutError());\n }, this.requestTimeoutMs);\n this.pending.set(id, { resolve, reject, timer });\n try {\n ws.send(JSON.stringify({ ...payload, id }));\n } catch (e) {\n clearTimeout(timer);\n this.pending.delete(id);\n reject(e instanceof Error ? e : new Error(String(e)));\n }\n });\n }\n\n private rejectAllPending(err: Error): void {\n for (const [, p] of this.pending) {\n clearTimeout(p.timer);\n p.reject(err);\n }\n this.pending.clear();\n }\n\n private setStatus(s: ConnectionStatus): void {\n if (this._status === s) return;\n this._status = s;\n for (const cb of this.statusListeners) {\n cb(s);\n }\n }\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@happenv/print-agent-sdk",
3
+ "version": "0.1.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "description": "Prosty SDK do lokalnego agenta druku (WebSocket): drukarki, druk PDF, status.",
8
+ "type": "module",
9
+ "main": "./dist/index.cjs",
10
+ "module": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "import": {
15
+ "types": "./dist/index.d.ts",
16
+ "default": "./dist/index.js"
17
+ },
18
+ "require": {
19
+ "types": "./dist/index.d.cts",
20
+ "default": "./dist/index.cjs"
21
+ }
22
+ }
23
+ },
24
+ "files": ["dist"],
25
+ "sideEffects": false,
26
+ "scripts": {
27
+ "build": "tsup",
28
+ "test": "vitest run",
29
+ "prepublishOnly": "npm run build"
30
+ },
31
+ "keywords": ["printing", "websocket", "pdf", "print-agent"],
32
+ "license": "MIT",
33
+ "devDependencies": {
34
+ "@types/ws": "^8.5.10",
35
+ "tsup": "^8.0.0",
36
+ "typescript": "^5.4.0",
37
+ "vitest": "^1.6.0",
38
+ "ws": "^8.16.0"
39
+ }
40
+ }