@ecobridge.xyz/devicemanager 3.0.2 → 3.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.
@@ -0,0 +1,45 @@
1
+ import type { IPrinterCapabilities, IPrintOptions, IPrintJob } from '../interfaces/index.js';
2
+ /**
3
+ * IPP protocol wrapper using the ipp npm package
4
+ */
5
+ export declare class IppProtocol {
6
+ private printerUrl;
7
+ private printer;
8
+ constructor(address: string, port: number, path?: string);
9
+ /**
10
+ * Get printer attributes/capabilities
11
+ */
12
+ getAttributes(): Promise<IPrinterCapabilities>;
13
+ /**
14
+ * Print a document
15
+ */
16
+ print(data: Buffer, options?: IPrintOptions): Promise<IPrintJob>;
17
+ /**
18
+ * Get all jobs
19
+ */
20
+ getJobs(): Promise<IPrintJob[]>;
21
+ /**
22
+ * Get specific job info
23
+ */
24
+ getJobInfo(jobId: number): Promise<IPrintJob>;
25
+ /**
26
+ * Cancel a job
27
+ */
28
+ cancelJob(jobId: number): Promise<void>;
29
+ /**
30
+ * Check if printer is available
31
+ */
32
+ checkAvailability(): Promise<boolean>;
33
+ /**
34
+ * Build IPP print message from options
35
+ */
36
+ private buildPrintMessage;
37
+ /**
38
+ * Parse printer capabilities from attributes
39
+ */
40
+ private parseCapabilities;
41
+ /**
42
+ * Parse job info from attributes
43
+ */
44
+ private parseJobInfo;
45
+ }
@@ -0,0 +1,284 @@
1
+ import * as plugins from '../plugins.js';
2
+ /**
3
+ * IPP protocol wrapper using the ipp npm package
4
+ */
5
+ export class IppProtocol {
6
+ printerUrl;
7
+ printer;
8
+ constructor(address, port, path = '/ipp/print') {
9
+ this.printerUrl = `ipp://${address}:${port}${path}`;
10
+ this.printer = plugins.ipp.Printer(this.printerUrl);
11
+ }
12
+ /**
13
+ * Get printer attributes/capabilities
14
+ */
15
+ async getAttributes() {
16
+ return new Promise((resolve, reject) => {
17
+ this.printer.execute('Get-Printer-Attributes', null, (err, res) => {
18
+ if (err) {
19
+ reject(err);
20
+ return;
21
+ }
22
+ try {
23
+ const attrs = res['printer-attributes-tag'] || {};
24
+ resolve(this.parseCapabilities(attrs));
25
+ }
26
+ catch (parseErr) {
27
+ reject(parseErr);
28
+ }
29
+ });
30
+ });
31
+ }
32
+ /**
33
+ * Print a document
34
+ */
35
+ async print(data, options) {
36
+ const msg = this.buildPrintMessage(options);
37
+ return new Promise((resolve, reject) => {
38
+ this.printer.execute('Print-Job', { ...msg, data }, (err, res) => {
39
+ if (err) {
40
+ reject(err);
41
+ return;
42
+ }
43
+ try {
44
+ const jobAttrs = res['job-attributes-tag'] || {};
45
+ resolve(this.parseJobInfo(jobAttrs));
46
+ }
47
+ catch (parseErr) {
48
+ reject(parseErr);
49
+ }
50
+ });
51
+ });
52
+ }
53
+ /**
54
+ * Get all jobs
55
+ */
56
+ async getJobs() {
57
+ return new Promise((resolve, reject) => {
58
+ this.printer.execute('Get-Jobs', {
59
+ 'operation-attributes-tag': {
60
+ 'requesting-user-name': 'devicemanager',
61
+ 'which-jobs': 'not-completed',
62
+ },
63
+ }, (err, res) => {
64
+ if (err) {
65
+ reject(err);
66
+ return;
67
+ }
68
+ try {
69
+ const jobs = [];
70
+ const jobTags = res['job-attributes-tag'];
71
+ if (Array.isArray(jobTags)) {
72
+ for (const jobAttrs of jobTags) {
73
+ jobs.push(this.parseJobInfo(jobAttrs));
74
+ }
75
+ }
76
+ else if (jobTags && typeof jobTags === 'object') {
77
+ jobs.push(this.parseJobInfo(jobTags));
78
+ }
79
+ resolve(jobs);
80
+ }
81
+ catch (parseErr) {
82
+ reject(parseErr);
83
+ }
84
+ });
85
+ });
86
+ }
87
+ /**
88
+ * Get specific job info
89
+ */
90
+ async getJobInfo(jobId) {
91
+ return new Promise((resolve, reject) => {
92
+ this.printer.execute('Get-Job-Attributes', {
93
+ 'operation-attributes-tag': {
94
+ 'job-id': jobId,
95
+ },
96
+ }, (err, res) => {
97
+ if (err) {
98
+ reject(err);
99
+ return;
100
+ }
101
+ try {
102
+ const jobAttrs = res['job-attributes-tag'] || {};
103
+ resolve(this.parseJobInfo(jobAttrs));
104
+ }
105
+ catch (parseErr) {
106
+ reject(parseErr);
107
+ }
108
+ });
109
+ });
110
+ }
111
+ /**
112
+ * Cancel a job
113
+ */
114
+ async cancelJob(jobId) {
115
+ return new Promise((resolve, reject) => {
116
+ this.printer.execute('Cancel-Job', {
117
+ 'operation-attributes-tag': {
118
+ 'job-id': jobId,
119
+ },
120
+ }, (err, _res) => {
121
+ if (err) {
122
+ reject(err);
123
+ return;
124
+ }
125
+ resolve();
126
+ });
127
+ });
128
+ }
129
+ /**
130
+ * Check if printer is available
131
+ */
132
+ async checkAvailability() {
133
+ try {
134
+ await this.getAttributes();
135
+ return true;
136
+ }
137
+ catch {
138
+ return false;
139
+ }
140
+ }
141
+ /**
142
+ * Build IPP print message from options
143
+ */
144
+ buildPrintMessage(options) {
145
+ const operationAttrs = {
146
+ 'requesting-user-name': 'devicemanager',
147
+ 'job-name': options?.jobName ?? 'Print Job',
148
+ 'document-format': 'application/octet-stream',
149
+ };
150
+ const jobAttrs = {};
151
+ if (options?.copies && options.copies > 1) {
152
+ jobAttrs['copies'] = options.copies;
153
+ }
154
+ if (options?.mediaSize) {
155
+ jobAttrs['media'] = options.mediaSize;
156
+ }
157
+ if (options?.mediaType) {
158
+ jobAttrs['media-type'] = options.mediaType;
159
+ }
160
+ if (options?.sides) {
161
+ jobAttrs['sides'] = options.sides;
162
+ }
163
+ if (options?.quality) {
164
+ const qualityMap = {
165
+ draft: 3,
166
+ normal: 4,
167
+ high: 5,
168
+ };
169
+ jobAttrs['print-quality'] = qualityMap[options.quality] ?? 4;
170
+ }
171
+ if (options?.colorMode) {
172
+ jobAttrs['print-color-mode'] = options.colorMode;
173
+ }
174
+ const msg = {
175
+ 'operation-attributes-tag': operationAttrs,
176
+ };
177
+ if (Object.keys(jobAttrs).length > 0) {
178
+ msg['job-attributes-tag'] = jobAttrs;
179
+ }
180
+ return msg;
181
+ }
182
+ /**
183
+ * Parse printer capabilities from attributes
184
+ */
185
+ parseCapabilities(attrs) {
186
+ const getArray = (key) => {
187
+ const value = attrs[key];
188
+ if (Array.isArray(value))
189
+ return value.map(String);
190
+ if (value !== undefined)
191
+ return [String(value)];
192
+ return [];
193
+ };
194
+ const getNumber = (key, defaultVal) => {
195
+ const value = attrs[key];
196
+ if (typeof value === 'number')
197
+ return value;
198
+ if (typeof value === 'string')
199
+ return parseInt(value) || defaultVal;
200
+ return defaultVal;
201
+ };
202
+ const getBool = (key, defaultVal) => {
203
+ const value = attrs[key];
204
+ if (typeof value === 'boolean')
205
+ return value;
206
+ if (value === 'true' || value === 1)
207
+ return true;
208
+ if (value === 'false' || value === 0)
209
+ return false;
210
+ return defaultVal;
211
+ };
212
+ // Parse resolutions
213
+ const resolutions = [];
214
+ const resSupported = attrs['printer-resolution-supported'];
215
+ if (Array.isArray(resSupported)) {
216
+ for (const res of resSupported) {
217
+ if (typeof res === 'object' && res !== null && 'x' in res) {
218
+ resolutions.push(res.x);
219
+ }
220
+ else if (typeof res === 'number') {
221
+ resolutions.push(res);
222
+ }
223
+ }
224
+ }
225
+ if (resolutions.length === 0) {
226
+ resolutions.push(300, 600);
227
+ }
228
+ return {
229
+ colorSupported: getBool('color-supported', false),
230
+ duplexSupported: getArray('sides-supported').some((s) => s.includes('two-sided')),
231
+ mediaSizes: getArray('media-supported'),
232
+ mediaTypes: getArray('media-type-supported'),
233
+ resolutions: [...new Set(resolutions)],
234
+ maxCopies: getNumber('copies-supported', 99),
235
+ sidesSupported: getArray('sides-supported'),
236
+ qualitySupported: getArray('print-quality-supported').map(String),
237
+ };
238
+ }
239
+ /**
240
+ * Parse job info from attributes
241
+ */
242
+ parseJobInfo(attrs) {
243
+ const getString = (key, defaultVal) => {
244
+ const value = attrs[key];
245
+ if (typeof value === 'string')
246
+ return value;
247
+ if (value !== undefined)
248
+ return String(value);
249
+ return defaultVal;
250
+ };
251
+ const getNumber = (key, defaultVal) => {
252
+ const value = attrs[key];
253
+ if (typeof value === 'number')
254
+ return value;
255
+ if (typeof value === 'string')
256
+ return parseInt(value) || defaultVal;
257
+ return defaultVal;
258
+ };
259
+ // Map IPP job state to our state
260
+ const ippState = getNumber('job-state', 3);
261
+ const stateMap = {
262
+ 3: 'pending', // pending
263
+ 4: 'pending', // pending-held
264
+ 5: 'processing', // processing
265
+ 6: 'processing', // processing-stopped
266
+ 7: 'canceled', // canceled
267
+ 8: 'aborted', // aborted
268
+ 9: 'completed', // completed
269
+ };
270
+ const createdTime = attrs['time-at-creation'];
271
+ const completedTime = attrs['time-at-completed'];
272
+ return {
273
+ id: getNumber('job-id', 0),
274
+ name: getString('job-name', 'Unknown Job'),
275
+ state: stateMap[ippState] ?? 'pending',
276
+ stateReason: getString('job-state-reasons', undefined),
277
+ createdAt: createdTime ? new Date(createdTime * 1000) : new Date(),
278
+ completedAt: completedTime ? new Date(completedTime * 1000) : undefined,
279
+ pagesPrinted: getNumber('job-media-sheets-completed', undefined),
280
+ pagesTotal: getNumber('job-media-sheets', undefined),
281
+ };
282
+ }
283
+ }
284
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJvdG9jb2wuaXBwLm9sZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL3Byb3RvY29scy9wcm90b2NvbC5pcHAub2xkLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sZUFBZSxDQUFDO0FBT3pDOztHQUVHO0FBQ0gsTUFBTSxPQUFPLFdBQVc7SUFDZCxVQUFVLENBQVM7SUFDbkIsT0FBTyxDQUF5QztJQUV4RCxZQUFZLE9BQWUsRUFBRSxJQUFZLEVBQUUsT0FBZSxZQUFZO1FBQ3BFLElBQUksQ0FBQyxVQUFVLEdBQUcsU0FBUyxPQUFPLElBQUksSUFBSSxHQUFHLElBQUksRUFBRSxDQUFDO1FBQ3BELElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxDQUFDO0lBQ3RELENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQyxhQUFhO1FBQ3hCLE9BQU8sSUFBSSxPQUFPLENBQUMsQ0FBQyxPQUFPLEVBQUUsTUFBTSxFQUFFLEVBQUU7WUFDckMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQ2xCLHdCQUF3QixFQUN4QixJQUFJLEVBQ0osQ0FBQyxHQUFpQixFQUFFLEdBQTRCLEVBQUUsRUFBRTtnQkFDbEQsSUFBSSxHQUFHLEVBQUUsQ0FBQztvQkFDUixNQUFNLENBQUMsR0FBRyxDQUFDLENBQUM7b0JBQ1osT0FBTztnQkFDVCxDQUFDO2dCQUVELElBQUksQ0FBQztvQkFDSCxNQUFNLEtBQUssR0FBRyxHQUFHLENBQUMsd0JBQXdCLENBQTRCLElBQUksRUFBRSxDQUFDO29CQUM3RSxPQUFPLENBQUMsSUFBSSxDQUFDLGlCQUFpQixDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUM7Z0JBQ3pDLENBQUM7Z0JBQUMsT0FBTyxRQUFRLEVBQUUsQ0FBQztvQkFDbEIsTUFBTSxDQUFDLFFBQVEsQ0FBQyxDQUFDO2dCQUNu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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecobridge.xyz/devicemanager",
3
- "version": "3.0.2",
3
+ "version": "3.1.0",
4
4
  "private": false,
5
5
  "description": "a device manager for talking to devices on network and over usb",
6
6
  "main": "dist_ts/index.js",
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@ecobridge.xyz/devicemanager',
6
- version: '3.0.2',
6
+ version: '3.1.0',
7
7
  description: 'a device manager for talking to devices on network and over usb'
8
8
  }
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { Feature, type TDeviceReference } from './feature.abstract.js';
7
- import { IppProtocol } from '../protocols/index.js';
7
+ import { IppProtocol, type IIppPrinterCapabilities, type IIppJob } from '../protocols/index.js';
8
8
  import type {
9
9
  TPrintProtocol,
10
10
  TPrintSides,
@@ -16,7 +16,6 @@ import type {
16
16
  IPrintFeatureInfo,
17
17
  IFeatureOptions,
18
18
  } from '../interfaces/feature.interfaces.js';
19
- import type { IPrinterCapabilities } from '../interfaces/index.js';
20
19
 
21
20
  /**
22
21
  * Options for creating a PrintFeature
@@ -101,7 +100,7 @@ export class PrintFeature extends Feature {
101
100
 
102
101
  this.ippClient = new IppProtocol(address, port, path);
103
102
  // Verify connection by getting printer attributes
104
- const attrs = await this.ippClient.getAttributes();
103
+ const attrs = await this.ippClient.getPrinterAttributes();
105
104
  this.updateCapabilitiesFromIpp(attrs);
106
105
  }
107
106
  // JetDirect and LPD don't need connection verification
@@ -154,7 +153,8 @@ export class PrintFeature extends Feature {
154
153
  throw new Error('Print feature not connected');
155
154
  }
156
155
 
157
- return this.ippClient.getJobs();
156
+ const jobs = await this.ippClient.getJobs();
157
+ return jobs.map(job => this.mapIppJobToInternal(job));
158
158
  }
159
159
 
160
160
  /**
@@ -165,7 +165,8 @@ export class PrintFeature extends Feature {
165
165
  throw new Error('Print feature not connected');
166
166
  }
167
167
 
168
- return this.ippClient.getJobInfo(jobId);
168
+ const job = await this.ippClient.getJobAttributes(jobId);
169
+ return this.mapIppJobToInternal(job);
169
170
  }
170
171
 
171
172
  /**
@@ -191,9 +192,17 @@ export class PrintFeature extends Feature {
191
192
 
192
193
  this.emit('print:started', options);
193
194
 
194
- // IppProtocol.print() accepts IPrintOptions and returns IPrintJob
195
- const job = await this.ippClient.print(data, options);
195
+ // Use smartPrint for auto format detection and conversion
196
+ const ippJob = await this.ippClient.smartPrint(data, {
197
+ jobName: options?.jobName,
198
+ copies: options?.copies,
199
+ media: options?.mediaSize,
200
+ sides: options?.sides,
201
+ printQuality: options?.quality,
202
+ colorMode: options?.colorMode,
203
+ });
196
204
 
205
+ const job = this.mapIppJobToInternal(ippJob);
197
206
  this.emit('print:submitted', job);
198
207
  return job;
199
208
  }
@@ -202,58 +211,57 @@ export class PrintFeature extends Feature {
202
211
  // Helper Methods
203
212
  // ============================================================================
204
213
 
205
- private updateCapabilitiesFromIpp(caps: IPrinterCapabilities): void {
214
+ private updateCapabilitiesFromIpp(caps: IIppPrinterCapabilities): void {
206
215
  this.supportsColor = caps.colorSupported;
207
- this.supportsDuplex = caps.duplexSupported;
208
- this.maxCopies = caps.maxCopies;
209
-
210
- if (caps.mediaSizes && caps.mediaSizes.length > 0) {
211
- this.supportedMediaSizes = caps.mediaSizes;
216
+ // Derive duplexSupported from sidesSupported
217
+ this.supportsDuplex = caps.sidesSupported?.some(s =>
218
+ s.includes('two-sided')
219
+ ) ?? false;
220
+ // Get max copies from range
221
+ this.maxCopies = caps.copiesSupported?.upper ?? 99;
222
+
223
+ if (caps.mediaSizeSupported && caps.mediaSizeSupported.length > 0) {
224
+ this.supportedMediaSizes = caps.mediaSizeSupported;
212
225
  }
213
- if (caps.mediaTypes && caps.mediaTypes.length > 0) {
214
- this.supportedMediaTypes = caps.mediaTypes;
226
+ if (caps.mediaTypeSupported && caps.mediaTypeSupported.length > 0) {
227
+ this.supportedMediaTypes = caps.mediaTypeSupported;
215
228
  }
216
229
  if (caps.sidesSupported && caps.sidesSupported.length > 0) {
217
230
  this.supportedSides = caps.sidesSupported.filter((s): s is TPrintSides =>
218
231
  ['one-sided', 'two-sided-long-edge', 'two-sided-short-edge'].includes(s)
219
232
  );
220
233
  }
221
- if (caps.qualitySupported && caps.qualitySupported.length > 0) {
222
- this.supportedQualities = caps.qualitySupported.filter((q): q is TPrintQuality =>
223
- ['draft', 'normal', 'high'].includes(q)
224
- );
225
- }
226
- }
227
-
228
- private qualityToIpp(quality: TPrintQuality): number {
229
- switch (quality) {
230
- case 'draft': return 3;
231
- case 'normal': return 4;
232
- case 'high': return 5;
233
- default: return 4;
234
+ // Map IPP quality values (3=draft, 4=normal, 5=high) to strings
235
+ if (caps.printQualitySupported && caps.printQualitySupported.length > 0) {
236
+ const qualityMap: Record<number, TPrintQuality> = { 3: 'draft', 4: 'normal', 5: 'high' };
237
+ this.supportedQualities = caps.printQualitySupported
238
+ .map(q => qualityMap[q])
239
+ .filter((q): q is TPrintQuality => q !== undefined);
234
240
  }
235
241
  }
236
242
 
237
- private mapIppJob(job: Record<string, unknown>): IPrintJob {
238
- const stateMap: Record<number, IPrintJob['state']> = {
239
- 3: 'pending',
240
- 4: 'pending',
241
- 5: 'processing',
242
- 6: 'processing',
243
- 7: 'canceled',
244
- 8: 'aborted',
245
- 9: 'completed',
243
+ /**
244
+ * Map IIppJob to IPrintJob, normalizing extended states
245
+ */
246
+ private mapIppJobToInternal(job: IIppJob): IPrintJob {
247
+ // Map extended IPP states to simpler internal states
248
+ const stateMap: Record<IIppJob['state'], IPrintJob['state']> = {
249
+ 'pending': 'pending',
250
+ 'pending-held': 'pending',
251
+ 'processing': 'processing',
252
+ 'processing-stopped': 'processing',
253
+ 'canceled': 'canceled',
254
+ 'aborted': 'aborted',
255
+ 'completed': 'completed',
246
256
  };
247
257
 
248
258
  return {
249
- id: job['job-id'] as number,
250
- name: job['job-name'] as string ?? 'Unknown',
251
- state: stateMap[(job['job-state'] as number) ?? 3] ?? 'pending',
252
- stateReason: (job['job-state-reasons'] as string[])?.[0],
253
- createdAt: new Date((job['time-at-creation'] as number) * 1000),
254
- completedAt: job['time-at-completed']
255
- ? new Date((job['time-at-completed'] as number) * 1000)
256
- : undefined,
259
+ id: job.id,
260
+ name: job.name,
261
+ state: stateMap[job.state],
262
+ stateReason: job.stateReasons?.[0],
263
+ createdAt: job.createdAt,
264
+ completedAt: job.completedAt,
257
265
  };
258
266
  }
259
267
 
@@ -10,7 +10,12 @@ export { EsclProtocol } from './protocol.escl.js';
10
10
  export { SaneProtocol } from './protocol.sane.js';
11
11
 
12
12
  // IPP printer protocol
13
- export { IppProtocol } from './protocol.ipp.js';
13
+ export {
14
+ IppProtocol,
15
+ type IIppPrinterCapabilities,
16
+ type IIppJob,
17
+ type IIppPrintOptions,
18
+ } from './protocol.ipp.js';
14
19
 
15
20
  // SNMP query protocol
16
21
  export {