@ecobridge.xyz/devicemanager 3.0.2 → 3.1.1

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.
Files changed (42) hide show
  1. package/.gitea/workflows/default_tags.yaml +0 -21
  2. package/.smartconfig.json +69 -0
  3. package/changelog.md +23 -1
  4. package/dist_ts/00_commitinfo_data.js +1 -1
  5. package/dist_ts/devicemanager.classes.devicemanager.js +33 -7
  6. package/dist_ts/discovery/discovery.classes.mdns.js +46 -11
  7. package/dist_ts/discovery/discovery.classes.networkscanner.d.ts +1 -1
  8. package/dist_ts/discovery/discovery.classes.networkscanner.js +1 -1
  9. package/dist_ts/discovery/discovery.classes.ssdp.d.ts +52 -21
  10. package/dist_ts/discovery/discovery.classes.ssdp.js +407 -117
  11. package/dist_ts/features/feature.print.d.ts +4 -2
  12. package/dist_ts/features/feature.print.js +48 -38
  13. package/dist_ts/index.d.ts +1 -1
  14. package/dist_ts/interfaces/feature.interfaces.d.ts +1 -1
  15. package/dist_ts/interfaces/index.d.ts +1 -1
  16. package/dist_ts/plugins.d.ts +5 -15
  17. package/dist_ts/plugins.js +6 -19
  18. package/dist_ts/protocols/index.d.ts +1 -1
  19. package/dist_ts/protocols/index.js +2 -2
  20. package/dist_ts/protocols/protocol.escl.js +2 -2
  21. package/dist_ts/protocols/protocol.ipp.d.ts +144 -33
  22. package/dist_ts/protocols/protocol.ipp.js +942 -239
  23. package/license.md +21 -0
  24. package/package.json +16 -25
  25. package/pnpm-workspace.yaml +4 -0
  26. package/readme.md +61 -76
  27. package/test/{test.ts → test.node.ts} +20 -45
  28. package/test/test.ssdp.node.ts +978 -0
  29. package/ts/00_commitinfo_data.ts +1 -1
  30. package/ts/devicemanager.classes.devicemanager.ts +44 -6
  31. package/ts/discovery/discovery.classes.mdns.ts +40 -11
  32. package/ts/discovery/discovery.classes.networkscanner.ts +1 -1
  33. package/ts/discovery/discovery.classes.ssdp.ts +493 -130
  34. package/ts/features/feature.print.ts +53 -45
  35. package/ts/index.ts +1 -0
  36. package/ts/interfaces/feature.interfaces.ts +1 -1
  37. package/ts/interfaces/index.ts +1 -1
  38. package/ts/plugins.ts +5 -25
  39. package/ts/protocols/index.ts +6 -1
  40. package/ts/protocols/protocol.escl.ts +1 -1
  41. package/ts/protocols/protocol.ipp.ts +1183 -260
  42. package/npmextra.json +0 -24
@@ -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
 
package/ts/index.ts CHANGED
@@ -160,6 +160,7 @@ export * from './interfaces/index.js';
160
160
  export type {
161
161
  ISsdpDevice,
162
162
  ISsdpDeviceDescription,
163
+ ISsdpDiscoveryDependencies,
163
164
  ISsdpService,
164
165
  ISsdpIcon,
165
166
  } from './discovery/discovery.classes.ssdp.js';
@@ -168,7 +168,7 @@ export interface IPrintJob {
168
168
  name: string;
169
169
  state: 'pending' | 'processing' | 'completed' | 'canceled' | 'aborted';
170
170
  stateReason?: string;
171
- createdAt: Date;
171
+ createdAt?: Date;
172
172
  completedAt?: Date;
173
173
  pagesPrinted?: number;
174
174
  pagesTotal?: number;
@@ -154,7 +154,7 @@ export interface IPrintJob {
154
154
  name: string;
155
155
  state: 'pending' | 'processing' | 'completed' | 'canceled' | 'aborted';
156
156
  stateReason?: string;
157
- createdAt: Date;
157
+ createdAt?: Date;
158
158
  completedAt?: Date;
159
159
  pagesPrinted?: number;
160
160
  pagesTotal?: number;
package/ts/plugins.ts CHANGED
@@ -1,41 +1,21 @@
1
1
  // native scope
2
2
  import * as path from 'path';
3
3
  import * as net from 'net';
4
- import * as dgram from 'dgram';
4
+ import * as dgram from 'node:dgram';
5
+ import * as os from 'node:os';
5
6
  import * as events from 'events';
6
7
 
7
- export { path, net, dgram, events };
8
+ export { path, net, dgram, os, events };
8
9
 
9
10
  // @push.rocks scope
10
11
  import * as smartpath from '@push.rocks/smartpath';
11
- import * as smartpromise from '@push.rocks/smartpromise';
12
12
  import * as smartdelay from '@push.rocks/smartdelay';
13
- import * as smartnetwork from '@push.rocks/smartnetwork';
14
13
 
15
- // Re-export SmartRequest for HTTP
16
- import { SmartRequest } from '@push.rocks/smartrequest';
17
- export { SmartRequest };
18
-
19
- export {
20
- smartpath,
21
- smartpromise,
22
- smartdelay,
23
- smartnetwork,
24
- };
14
+ export { smartpath, smartdelay };
25
15
 
26
16
  // third party
27
17
  import * as bonjourService from 'bonjour-service';
28
- import ipp from 'ipp';
29
- import nodeSsdpModule from 'node-ssdp';
30
18
  import * as netSnmp from 'net-snmp';
31
- import * as sonos from 'sonos';
32
- import * as castv2Client from 'castv2-client';
33
19
  import WebSocket from 'ws';
34
20
 
35
- // node-ssdp exports Client/Server under default in ESM
36
- const nodeSsdp = {
37
- Client: nodeSsdpModule.Client,
38
- Server: nodeSsdpModule.Server,
39
- };
40
-
41
- export { bonjourService, ipp, nodeSsdp, netSnmp, sonos, castv2Client, WebSocket };
21
+ export { bonjourService, netSnmp, WebSocket };
@@ -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 {
@@ -118,7 +118,7 @@ export class EsclProtocol {
118
118
  public async submitScanJob(options: IScanOptions): Promise<string> {
119
119
  const scanSettings = this.buildScanSettings(options);
120
120
 
121
- // Use fetch for POST with body since SmartRequest API may not support raw body
121
+ // Use fetch for the raw XML request body.
122
122
  const response = await fetch(`${this.baseUrl}/ScanJobs`, {
123
123
  method: 'POST',
124
124
  headers: {