@ecobridge.xyz/devicemanager 3.1.0 → 3.2.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.
Files changed (78) hide show
  1. package/.gitea/workflows/default_tags.yaml +0 -21
  2. package/.smartconfig.json +69 -0
  3. package/changelog.md +38 -1
  4. package/dist_ts/00_commitinfo_data.js +1 -1
  5. package/dist_ts/device/device.classes.device.d.ts +2 -2
  6. package/dist_ts/device/device.classes.device.js +3 -3
  7. package/dist_ts/devicemanager.classes.devicemanager.d.ts +17 -4
  8. package/dist_ts/devicemanager.classes.devicemanager.js +99 -11
  9. package/dist_ts/discovery/discovery.classes.mdns.js +46 -11
  10. package/dist_ts/discovery/discovery.classes.networkscanner.d.ts +5 -1
  11. package/dist_ts/discovery/discovery.classes.networkscanner.js +44 -3
  12. package/dist_ts/discovery/discovery.classes.ssdp.d.ts +52 -21
  13. package/dist_ts/discovery/discovery.classes.ssdp.js +407 -117
  14. package/dist_ts/events/events.observable.d.ts +15 -0
  15. package/dist_ts/events/events.observable.js +22 -0
  16. package/dist_ts/events/index.d.ts +1 -0
  17. package/dist_ts/events/index.js +2 -0
  18. package/dist_ts/factories/index.js +7 -5
  19. package/dist_ts/features/feature.abstract.d.ts +2 -2
  20. package/dist_ts/features/feature.abstract.js +3 -3
  21. package/dist_ts/features/feature.print.d.ts +3 -0
  22. package/dist_ts/features/feature.print.js +17 -2
  23. package/dist_ts/features/feature.scan.d.ts +5 -2
  24. package/dist_ts/features/feature.scan.js +98 -27
  25. package/dist_ts/index.d.ts +4 -2
  26. package/dist_ts/index.js +4 -2
  27. package/dist_ts/interfaces/feature.interfaces.d.ts +22 -2
  28. package/dist_ts/interfaces/index.d.ts +22 -4
  29. package/dist_ts/interfaces/index.js +1 -1
  30. package/dist_ts/plugins.d.ts +6 -15
  31. package/dist_ts/plugins.js +7 -19
  32. package/dist_ts/protocols/index.d.ts +2 -1
  33. package/dist_ts/protocols/index.js +4 -2
  34. package/dist_ts/protocols/protocol.brother.d.ts +67 -0
  35. package/dist_ts/protocols/protocol.brother.js +560 -0
  36. package/dist_ts/protocols/protocol.escl.d.ts +13 -4
  37. package/dist_ts/protocols/protocol.escl.js +345 -178
  38. package/dist_ts/protocols/protocol.ipp.d.ts +2 -0
  39. package/dist_ts/protocols/protocol.ipp.js +22 -2
  40. package/dist_ts/providers/index.d.ts +1 -0
  41. package/dist_ts/providers/index.js +2 -0
  42. package/dist_ts/providers/provider.smb.d.ts +60 -0
  43. package/dist_ts/providers/provider.smb.js +224 -0
  44. package/license.md +21 -0
  45. package/package.json +15 -20
  46. package/pnpm-workspace.yaml +6 -0
  47. package/readme.md +157 -78
  48. package/test/test.brother.node.ts +306 -0
  49. package/test/test.escl.node.ts +233 -0
  50. package/test/test.ipp.node.ts +75 -0
  51. package/test/{test.ts → test.node.ts} +37 -45
  52. package/test/test.smb.node.ts +123 -0
  53. package/test/test.ssdp.node.ts +978 -0
  54. package/ts/00_commitinfo_data.ts +1 -1
  55. package/ts/device/device.classes.device.ts +2 -2
  56. package/ts/devicemanager.classes.devicemanager.ts +125 -12
  57. package/ts/discovery/discovery.classes.mdns.ts +40 -11
  58. package/ts/discovery/discovery.classes.networkscanner.ts +51 -2
  59. package/ts/discovery/discovery.classes.ssdp.ts +493 -130
  60. package/ts/events/events.observable.ts +31 -0
  61. package/ts/events/index.ts +4 -0
  62. package/ts/factories/index.ts +7 -5
  63. package/ts/features/feature.abstract.ts +2 -2
  64. package/ts/features/feature.print.ts +16 -1
  65. package/ts/features/feature.scan.ts +93 -27
  66. package/ts/index.ts +21 -0
  67. package/ts/interfaces/feature.interfaces.ts +23 -2
  68. package/ts/interfaces/index.ts +23 -4
  69. package/ts/plugins.ts +6 -25
  70. package/ts/protocols/index.ts +11 -1
  71. package/ts/protocols/protocol.brother.ts +648 -0
  72. package/ts/protocols/protocol.escl.ts +348 -191
  73. package/ts/protocols/protocol.ipp.ts +24 -1
  74. package/ts/providers/index.ts +7 -0
  75. package/ts/providers/provider.smb.ts +326 -0
  76. package/dist_ts/protocols/protocol.ipp.old.d.ts +0 -45
  77. package/dist_ts/protocols/protocol.ipp.old.js +0 -284
  78. package/npmextra.json +0 -24
@@ -0,0 +1,31 @@
1
+ import * as plugins from '../plugins.js';
2
+
3
+ export interface IObservableEvent<TName extends string = string> {
4
+ name: TName;
5
+ args: readonly unknown[];
6
+ emittedAt: Date;
7
+ }
8
+
9
+ /**
10
+ * EventEmitter-compatible base that mirrors every named event to RxJS.
11
+ */
12
+ export class ObservableEventEmitter extends plugins.events.EventEmitter {
13
+ private readonly eventSubject = new plugins.rxjs.Subject<IObservableEvent>();
14
+
15
+ public readonly events$ = this.eventSubject.asObservable();
16
+
17
+ public override emit(eventName: string | symbol, ...args: unknown[]): boolean {
18
+ if (typeof eventName === 'string') {
19
+ this.eventSubject.next({
20
+ name: eventName,
21
+ args,
22
+ emittedAt: new Date(),
23
+ });
24
+ }
25
+ return super.emit(eventName, ...args);
26
+ }
27
+
28
+ protected completeEventStream(): void {
29
+ this.eventSubject.complete();
30
+ }
31
+ }
@@ -0,0 +1,4 @@
1
+ export {
2
+ ObservableEventEmitter,
3
+ type IObservableEvent,
4
+ } from './events.observable.js';
@@ -43,10 +43,12 @@ export function createScanner(
43
43
  const isSecure = info.txtRecords['TLS'] === '1' || (protocol === 'escl' && info.port === 443);
44
44
 
45
45
  // Parse capabilities from TXT records
46
- const formats = parseScanFormats(info.txtRecords);
47
- const resolutions = parseScanResolutions(info.txtRecords);
48
- const colorModes = parseScanColorModes(info.txtRecords);
49
- const sources = parseScanSources(info.txtRecords);
46
+ const formats = protocol === 'brother' ? ['jpeg' as const] : parseScanFormats(info.txtRecords);
47
+ const resolutions = protocol === 'brother' ? [100, 150, 200, 300, 600] : parseScanResolutions(info.txtRecords);
48
+ const colorModes = protocol === 'brother' ? ['color' as const] : parseScanColorModes(info.txtRecords);
49
+ const sources = protocol === 'brother'
50
+ ? ['flatbed' as const, 'adf' as const, 'adf-duplex' as const]
51
+ : parseScanSources(info.txtRecords);
50
52
 
51
53
  const device = new UniversalDevice(info.address, info.port, {
52
54
  name: info.name,
@@ -60,7 +62,7 @@ export function createScanner(
60
62
 
61
63
  // Add scan feature
62
64
  const scanFeature = new ScanFeature(device.getDeviceReference(), info.port, {
63
- protocol: protocol as 'escl' | 'sane',
65
+ protocol: protocol as 'brother' | 'escl' | 'sane',
64
66
  secure: isSecure,
65
67
  supportedFormats: formats,
66
68
  supportedResolutions: resolutions,
@@ -3,7 +3,6 @@
3
3
  * Features are composable capabilities that can be attached to devices
4
4
  */
5
5
 
6
- import * as plugins from '../plugins.js';
7
6
  import type {
8
7
  TFeatureType,
9
8
  TFeatureState,
@@ -13,6 +12,7 @@ import type {
13
12
  } from '../interfaces/feature.interfaces.js';
14
13
  import type { IRetryOptions } from '../interfaces/index.js';
15
14
  import { withRetry } from '../helpers/helpers.retry.js';
15
+ import { ObservableEventEmitter } from '../events/index.js';
16
16
 
17
17
  /**
18
18
  * Forward reference to Device to avoid circular dependency
@@ -29,7 +29,7 @@ export type TDeviceReference = {
29
29
  * Abstract base class for all features
30
30
  * Provides common functionality for connection management, state tracking, and retry logic
31
31
  */
32
- export abstract class Feature extends plugins.events.EventEmitter implements IFeature {
32
+ export abstract class Feature extends ObservableEventEmitter implements IFeature {
33
33
  /**
34
34
  * The feature type identifier
35
35
  */
@@ -58,6 +58,9 @@ export class PrintFeature extends Feature {
58
58
  // Capabilities
59
59
  public supportsColor: boolean = true;
60
60
  public supportsDuplex: boolean = false;
61
+ public airPrintSupported: boolean = false;
62
+ public supportedDocumentFormats: string[] = [];
63
+ public supportedResolutions: number[] = [300, 600];
61
64
  public supportedMediaSizes: string[] = ['iso_a4_210x297mm', 'na_letter_8.5x11in'];
62
65
  public supportedMediaTypes: string[] = ['stationery'];
63
66
  public maxCopies: number = 99;
@@ -119,11 +122,13 @@ export class PrintFeature extends Feature {
119
122
  */
120
123
  public async getCapabilities(): Promise<IPrintCapabilities> {
121
124
  return {
125
+ airPrintSupported: this.airPrintSupported,
122
126
  colorSupported: this.supportsColor,
123
127
  duplexSupported: this.supportsDuplex,
128
+ documentFormats: this.supportedDocumentFormats,
124
129
  mediaSizes: this.supportedMediaSizes,
125
130
  mediaTypes: this.supportedMediaTypes,
126
- resolutions: [300, 600],
131
+ resolutions: this.supportedResolutions,
127
132
  maxCopies: this.maxCopies,
128
133
  sidesSupported: this.supportedSides,
129
134
  qualitySupported: this.supportedQualities,
@@ -212,6 +217,8 @@ export class PrintFeature extends Feature {
212
217
  // ============================================================================
213
218
 
214
219
  private updateCapabilitiesFromIpp(caps: IIppPrinterCapabilities): void {
220
+ this.airPrintSupported = caps.airPrintSupported;
221
+ this.supportedDocumentFormats = caps.documentFormatSupported;
215
222
  this.supportsColor = caps.colorSupported;
216
223
  // Derive duplexSupported from sidesSupported
217
224
  this.supportsDuplex = caps.sidesSupported?.some(s =>
@@ -219,6 +226,12 @@ export class PrintFeature extends Feature {
219
226
  ) ?? false;
220
227
  // Get max copies from range
221
228
  this.maxCopies = caps.copiesSupported?.upper ?? 99;
229
+ const resolutions = caps.resolutionsSupported
230
+ .filter((resolution) => resolution.units === 'dpi' && resolution.crossFeed === resolution.feed)
231
+ .map((resolution) => resolution.crossFeed);
232
+ if (resolutions.length > 0) {
233
+ this.supportedResolutions = [...new Set(resolutions)].sort((left, right) => left - right);
234
+ }
222
235
 
223
236
  if (caps.mediaSizeSupported && caps.mediaSizeSupported.length > 0) {
224
237
  this.supportedMediaSizes = caps.mediaSizeSupported;
@@ -274,8 +287,10 @@ export class PrintFeature extends Feature {
274
287
  ...this.getBaseFeatureInfo(),
275
288
  type: 'print',
276
289
  protocol: this.protocol,
290
+ airPrintSupported: this.airPrintSupported,
277
291
  supportsColor: this.supportsColor,
278
292
  supportsDuplex: this.supportsDuplex,
293
+ supportedDocumentFormats: this.supportedDocumentFormats,
279
294
  supportedMediaSizes: this.supportedMediaSizes,
280
295
  };
281
296
  }
@@ -1,10 +1,11 @@
1
1
  /**
2
2
  * Scan Feature
3
- * Provides document scanning capability using eSCL or SANE protocols
3
+ * Provides document scanning capability using Brother native, eSCL, or SANE protocols
4
4
  */
5
5
 
6
6
  import { Feature, type TDeviceReference } from './feature.abstract.js';
7
- import { EsclProtocol, SaneProtocol } from '../protocols/index.js';
7
+ import { BrotherScanProtocol, EsclProtocol, SaneProtocol } from '../protocols/index.js';
8
+ import type { IEsclCapabilities } from '../interfaces/index.js';
8
9
  import type {
9
10
  TScanProtocol,
10
11
  TScanFormat,
@@ -35,7 +36,7 @@ export interface IScanFeatureOptions extends IFeatureOptions {
35
36
  /**
36
37
  * Scan Feature - provides document scanning capability
37
38
  *
38
- * Wraps eSCL (AirScan) or SANE protocols to provide a unified scanning interface.
39
+ * Wraps Brother native, eSCL (AirScan), or SANE protocols behind one interface.
39
40
  *
40
41
  * @example
41
42
  * ```typescript
@@ -52,6 +53,7 @@ export class ScanFeature extends Feature {
52
53
  public readonly protocol: TScanProtocol;
53
54
 
54
55
  // Protocol clients
56
+ private brotherClient: BrotherScanProtocol | null = null;
55
57
  private esclClient: EsclProtocol | null = null;
56
58
  private saneClient: SaneProtocol | null = null;
57
59
 
@@ -95,7 +97,11 @@ export class ScanFeature extends Feature {
95
97
  // ============================================================================
96
98
 
97
99
  protected async doConnect(): Promise<void> {
98
- if (this.protocol === 'escl') {
100
+ if (this.protocol === 'brother') {
101
+ this.brotherClient = new BrotherScanProtocol(this.address, this._port);
102
+ await this.brotherClient.connect();
103
+ this.updateCapabilitiesFromBrother(this.brotherClient.getCapabilities());
104
+ } else if (this.protocol === 'escl') {
99
105
  this.esclClient = new EsclProtocol(this.address, this._port, this.isSecure);
100
106
  // Fetch capabilities to verify connection
101
107
  const caps = await this.esclClient.getCapabilities();
@@ -111,6 +117,10 @@ export class ScanFeature extends Feature {
111
117
  }
112
118
 
113
119
  protected async doDisconnect(): Promise<void> {
120
+ if (this.brotherClient) {
121
+ await this.brotherClient.disconnect();
122
+ this.brotherClient = null;
123
+ }
114
124
  if (this.saneClient) {
115
125
  try {
116
126
  await this.saneClient.close();
@@ -153,7 +163,9 @@ export class ScanFeature extends Feature {
153
163
 
154
164
  const opts = this.resolveOptions(options);
155
165
 
156
- if (this.protocol === 'escl' && this.esclClient) {
166
+ if (this.protocol === 'brother' && this.brotherClient) {
167
+ return this.scanWithBrother(opts);
168
+ } else if (this.protocol === 'escl' && this.esclClient) {
157
169
  return this.scanWithEscl(opts);
158
170
  } else if (this.protocol === 'sane' && this.saneClient) {
159
171
  return this.scanWithSane(opts);
@@ -166,10 +178,13 @@ export class ScanFeature extends Feature {
166
178
  * Cancel an ongoing scan
167
179
  */
168
180
  public async cancelScan(): Promise<void> {
169
- if (this.protocol === 'sane' && this.saneClient) {
181
+ if (this.protocol === 'brother' && this.brotherClient) {
182
+ await this.brotherClient.cancel();
183
+ } else if (this.protocol === 'escl' && this.esclClient) {
184
+ await this.esclClient.cancelCurrentScan();
185
+ } else if (this.protocol === 'sane' && this.saneClient) {
170
186
  await this.saneClient.cancel();
171
187
  }
172
- // eSCL cancellation is handled by deleting the job
173
188
  this.emit('scan:cancelled');
174
189
  }
175
190
 
@@ -177,6 +192,17 @@ export class ScanFeature extends Feature {
177
192
  // Protocol-Specific Scanning
178
193
  // ============================================================================
179
194
 
195
+ private async scanWithBrother(options: Required<IScanOptions>): Promise<IScanResult> {
196
+ if (!this.brotherClient) {
197
+ throw new Error('Brother scan client not initialized');
198
+ }
199
+
200
+ this.emit('scan:started', options);
201
+ const result = await this.brotherClient.scan(options);
202
+ this.emit('scan:completed', result);
203
+ return result;
204
+ }
205
+
180
206
  private async scanWithEscl(options: Required<IScanOptions>): Promise<IScanResult> {
181
207
  if (!this.esclClient) {
182
208
  throw new Error('eSCL client not initialized');
@@ -229,7 +255,9 @@ export class ScanFeature extends Feature {
229
255
  resolution: options?.resolution ?? 300,
230
256
  format: options?.format ?? 'jpeg',
231
257
  colorMode: options?.colorMode ?? 'color',
232
- source: options?.source ?? 'flatbed',
258
+ source: options?.source ?? (
259
+ this.hasDuplex ? 'adf-duplex' : this.hasAdf ? 'adf' : 'flatbed'
260
+ ),
233
261
  area: options?.area ?? {
234
262
  x: 0,
235
263
  y: 0,
@@ -241,26 +269,43 @@ export class ScanFeature extends Feature {
241
269
  };
242
270
  }
243
271
 
244
- private updateCapabilitiesFromEscl(caps: any): void {
245
- if (caps.platen) {
246
- this.supportedResolutions = caps.platen.supportedResolutions || this.supportedResolutions;
247
- this.maxWidth = caps.platen.maxWidth || this.maxWidth;
248
- this.maxHeight = caps.platen.maxHeight || this.maxHeight;
249
- this.minWidth = caps.platen.minWidth || this.minWidth;
250
- this.minHeight = caps.platen.minHeight || this.minHeight;
251
- }
252
- if (caps.adf) {
253
- this.hasAdf = true;
254
- if (!this.supportedSources.includes('adf')) {
255
- this.supportedSources.push('adf');
256
- }
257
- }
258
- if (caps.adfDuplex) {
259
- this.hasDuplex = true;
260
- if (!this.supportedSources.includes('adf-duplex')) {
261
- this.supportedSources.push('adf-duplex');
262
- }
272
+ private updateCapabilitiesFromBrother(caps: IScanCapabilities): void {
273
+ this.supportedResolutions = caps.resolutions;
274
+ this.supportedFormats = caps.formats;
275
+ this.supportedColorModes = caps.colorModes;
276
+ this.supportedSources = caps.sources;
277
+ this.hasAdf = caps.sources.includes('adf') || caps.sources.includes('adf-duplex');
278
+ this.hasDuplex = caps.sources.includes('adf-duplex');
279
+ this.maxWidth = caps.maxWidth;
280
+ this.maxHeight = caps.maxHeight;
281
+ this.minWidth = caps.minWidth;
282
+ this.minHeight = caps.minHeight;
283
+ }
284
+
285
+ private updateCapabilitiesFromEscl(caps: IEsclCapabilities): void {
286
+ const sources = [caps.platen, caps.adf, caps.adfDuplex].filter((source) => source !== undefined);
287
+ if (sources.length > 0) {
288
+ this.supportedResolutions = [...new Set(
289
+ sources.flatMap((source) => source.supportedResolutions)
290
+ )].sort((left, right) => left - right);
291
+ this.maxWidth = Math.max(...sources.map((source) => source.maxWidth));
292
+ this.maxHeight = Math.max(...sources.map((source) => source.maxHeight));
293
+ this.minWidth = Math.min(...sources.map((source) => source.minWidth));
294
+ this.minHeight = Math.min(...sources.map((source) => source.minHeight));
263
295
  }
296
+
297
+ const supportedSources: TScanSource[] = [];
298
+ if (caps.platen) supportedSources.push('flatbed');
299
+ if (caps.adf) supportedSources.push('adf');
300
+ if (caps.adfDuplex) supportedSources.push('adf-duplex');
301
+ if (supportedSources.length > 0) this.supportedSources = supportedSources;
302
+ this.hasAdf = Boolean(caps.adf || caps.adfDuplex);
303
+ this.hasDuplex = Boolean(caps.adfDuplex);
304
+
305
+ const formats = mapDocumentFormats(sources.flatMap((source) => source.documentFormats));
306
+ const colorModes = mapColorModes(sources.flatMap((source) => source.colorModes));
307
+ if (formats.length > 0) this.supportedFormats = formats;
308
+ if (colorModes.length > 0) this.supportedColorModes = colorModes;
264
309
  }
265
310
 
266
311
  private colorModeToSane(mode: TColorMode): string {
@@ -329,6 +374,27 @@ export class ScanFeature extends Feature {
329
374
  }
330
375
  }
331
376
 
377
+ function mapDocumentFormats(formats: string[]): TScanFormat[] {
378
+ const mapped = formats.flatMap((format): TScanFormat[] => {
379
+ if (format === 'application/pdf') return ['pdf'];
380
+ if (format === 'image/jpeg') return ['jpeg'];
381
+ if (format === 'image/png') return ['png'];
382
+ if (format === 'image/tiff') return ['tiff'];
383
+ return [];
384
+ });
385
+ return [...new Set(mapped)];
386
+ }
387
+
388
+ function mapColorModes(modes: string[]): TColorMode[] {
389
+ const mapped = modes.flatMap((mode): TColorMode[] => {
390
+ if (mode.startsWith('RGB')) return ['color'];
391
+ if (mode.startsWith('Grayscale')) return ['grayscale'];
392
+ if (mode.startsWith('BlackAndWhite')) return ['blackwhite'];
393
+ return [];
394
+ });
395
+ return [...new Set(mapped)];
396
+ }
397
+
332
398
  // ============================================================================
333
399
  // Parsing Helpers
334
400
  // ============================================================================
package/ts/index.ts CHANGED
@@ -17,6 +17,19 @@ export {
17
17
  SSDP_SERVICE_TYPES,
18
18
  } from './devicemanager.classes.devicemanager.js';
19
19
 
20
+ export {
21
+ ObservableEventEmitter,
22
+ type IObservableEvent,
23
+ } from './events/index.js';
24
+
25
+ export {
26
+ SmbShareProvider,
27
+ type ISmbDocumentEvent,
28
+ type ISmbServerAdapter,
29
+ type ISmbShareProviderDependencies,
30
+ type ISmbShareProviderOptions,
31
+ } from './providers/index.js';
32
+
20
33
  // ============================================================================
21
34
  // Universal Device & Features
22
35
  // ============================================================================
@@ -99,6 +112,9 @@ export {
99
112
 
100
113
  export {
101
114
  EsclProtocol,
115
+ EsclScanError,
116
+ BrotherScanProtocol,
117
+ BrotherScanError,
102
118
  SaneProtocol,
103
119
  IppProtocol,
104
120
  SnmpProtocol,
@@ -114,6 +130,10 @@ export {
114
130
  // Home Assistant protocol
115
131
  HomeAssistantProtocol,
116
132
  type ISnmpOptions,
133
+ type IBrotherScanLease,
134
+ type IBrotherScanOptions,
135
+ type IBrotherScanProtocolOptions,
136
+ type TBrotherScanErrorCode,
117
137
  type ISnmpVarbind,
118
138
  type TSnmpValueType,
119
139
  type TNutStatusFlag,
@@ -160,6 +180,7 @@ export * from './interfaces/index.js';
160
180
  export type {
161
181
  ISsdpDevice,
162
182
  ISsdpDeviceDescription,
183
+ ISsdpDiscoveryDependencies,
163
184
  ISsdpService,
164
185
  ISsdpIcon,
165
186
  } from './discovery/discovery.classes.ssdp.js';
@@ -79,7 +79,7 @@ export interface IFeatureInfo {
79
79
  // Scan Feature
80
80
  // ============================================================================
81
81
 
82
- export type TScanProtocol = 'escl' | 'sane' | 'wia';
82
+ export type TScanProtocol = 'brother' | 'escl' | 'sane' | 'wia';
83
83
  export type TScanFormat = 'png' | 'jpeg' | 'pdf' | 'tiff';
84
84
  export type TColorMode = 'color' | 'grayscale' | 'blackwhite';
85
85
  export type TScanSource = 'flatbed' | 'adf' | 'adf-duplex';
@@ -120,6 +120,23 @@ export interface IScanResult {
120
120
  resolution: number;
121
121
  colorMode: TColorMode;
122
122
  mimeType: string;
123
+ /** Number of documents returned by a multipage-capable scanner transport */
124
+ pageCount?: number;
125
+ /** All returned documents, including the first document exposed as data */
126
+ pages?: IScanPage[];
127
+ /** Whether the scanner reported a cleanly completed job */
128
+ isComplete?: boolean;
129
+ /** Scanner reason when a partial result is returned with an error */
130
+ jobStateReason?: string;
131
+ /** Differences detected between requested and returned scan settings */
132
+ settingsWarnings?: string[];
133
+ }
134
+
135
+ export interface IScanPage {
136
+ data: Buffer;
137
+ width: number;
138
+ height: number;
139
+ mimeType: string;
123
140
  }
124
141
 
125
142
  export interface IScanFeatureInfo extends IFeatureInfo {
@@ -143,8 +160,10 @@ export type TPrintQuality = 'draft' | 'normal' | 'high';
143
160
  export type TPrintColorMode = 'color' | 'monochrome';
144
161
 
145
162
  export interface IPrintCapabilities {
163
+ airPrintSupported: boolean;
146
164
  colorSupported: boolean;
147
165
  duplexSupported: boolean;
166
+ documentFormats: string[];
148
167
  mediaSizes: string[];
149
168
  mediaTypes: string[];
150
169
  resolutions: number[];
@@ -168,7 +187,7 @@ export interface IPrintJob {
168
187
  name: string;
169
188
  state: 'pending' | 'processing' | 'completed' | 'canceled' | 'aborted';
170
189
  stateReason?: string;
171
- createdAt: Date;
190
+ createdAt?: Date;
172
191
  completedAt?: Date;
173
192
  pagesPrinted?: number;
174
193
  pagesTotal?: number;
@@ -177,8 +196,10 @@ export interface IPrintJob {
177
196
  export interface IPrintFeatureInfo extends IFeatureInfo {
178
197
  type: 'print';
179
198
  protocol: TPrintProtocol;
199
+ airPrintSupported: boolean;
180
200
  supportsColor: boolean;
181
201
  supportsDuplex: boolean;
202
+ supportedDocumentFormats: string[];
182
203
  supportedMediaSizes: string[];
183
204
  }
184
205
 
@@ -14,7 +14,7 @@ export type TConnectionState = 'disconnected' | 'connecting' | 'connected' | 'er
14
14
  // Scanner Types
15
15
  // ============================================================================
16
16
 
17
- export type TScannerProtocol = 'sane' | 'escl';
17
+ export type TScannerProtocol = 'brother' | 'sane' | 'escl';
18
18
  export type TScanFormat = 'png' | 'jpeg' | 'pdf' | 'tiff';
19
19
  export type TColorMode = 'color' | 'grayscale' | 'blackwhite';
20
20
  export type TScanSource = 'flatbed' | 'adf' | 'adf-duplex';
@@ -105,6 +105,23 @@ export interface IScanResult {
105
105
  colorMode: TColorMode;
106
106
  /** MIME type */
107
107
  mimeType: string;
108
+ /** Number of documents returned by a multipage-capable scanner transport */
109
+ pageCount?: number;
110
+ /** All returned documents, including the first document exposed as data */
111
+ pages?: IScanPage[];
112
+ /** Whether the scanner reported a cleanly completed job */
113
+ isComplete?: boolean;
114
+ /** Scanner reason when a partial result is returned with an error */
115
+ jobStateReason?: string;
116
+ /** Differences detected between requested and returned scan settings */
117
+ settingsWarnings?: string[];
118
+ }
119
+
120
+ export interface IScanPage {
121
+ data: Buffer;
122
+ width: number;
123
+ height: number;
124
+ mimeType: string;
108
125
  }
109
126
 
110
127
  export interface IScannerCapabilities {
@@ -154,7 +171,7 @@ export interface IPrintJob {
154
171
  name: string;
155
172
  state: 'pending' | 'processing' | 'completed' | 'canceled' | 'aborted';
156
173
  stateReason?: string;
157
- createdAt: Date;
174
+ createdAt?: Date;
158
175
  completedAt?: Date;
159
176
  pagesPrinted?: number;
160
177
  pagesTotal?: number;
@@ -321,8 +338,10 @@ export interface INetworkScanOptions {
321
338
  concurrency?: number;
322
339
  /** Timeout per probe in milliseconds (default: 2000) */
323
340
  timeout?: number;
324
- /** Ports to probe (default: [80, 443, 631, 6566, 9100, 7000, 1400, 8009]) */
341
+ /** Ports to probe (includes Brother native scan on 54921 by default) */
325
342
  ports?: number[];
343
+ /** Check for Brother native network scanners (default: true) */
344
+ probeBrother?: boolean;
326
345
  /** Check for eSCL scanners (default: true) */
327
346
  probeEscl?: boolean;
328
347
  /** Check for IPP printers (default: true) */
@@ -339,7 +358,7 @@ export interface INetworkScanOptions {
339
358
 
340
359
  export interface INetworkScanDevice {
341
360
  type: 'scanner' | 'printer' | 'speaker';
342
- protocol: 'escl' | 'sane' | 'ipp' | 'jetdirect' | 'airplay' | 'sonos' | 'chromecast';
361
+ protocol: 'brother' | 'escl' | 'sane' | 'ipp' | 'jetdirect' | 'airplay' | 'sonos' | 'chromecast';
343
362
  port: number;
344
363
  name?: string;
345
364
  model?: string;
package/ts/plugins.ts CHANGED
@@ -1,41 +1,22 @@
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';
20
+ import * as rxjs from 'rxjs';
34
21
 
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 };
22
+ export { bonjourService, netSnmp, WebSocket, rxjs };
@@ -4,7 +4,17 @@
4
4
  */
5
5
 
6
6
  // eSCL/AirScan scanner protocol
7
- export { EsclProtocol } from './protocol.escl.js';
7
+ export { EsclProtocol, EsclScanError } from './protocol.escl.js';
8
+
9
+ // Brother native network scanner protocol
10
+ export {
11
+ BrotherScanProtocol,
12
+ BrotherScanError,
13
+ type IBrotherScanLease,
14
+ type IBrotherScanOptions,
15
+ type IBrotherScanProtocolOptions,
16
+ type TBrotherScanErrorCode,
17
+ } from './protocol.brother.js';
8
18
 
9
19
  // SANE network scanner protocol
10
20
  export { SaneProtocol } from './protocol.sane.js';