@ecobridge.xyz/devicemanager 3.1.1 → 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.
- package/changelog.md +25 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/device/device.classes.device.d.ts +2 -2
- package/dist_ts/device/device.classes.device.js +3 -3
- package/dist_ts/devicemanager.classes.devicemanager.d.ts +17 -4
- package/dist_ts/devicemanager.classes.devicemanager.js +67 -5
- package/dist_ts/discovery/discovery.classes.networkscanner.d.ts +4 -0
- package/dist_ts/discovery/discovery.classes.networkscanner.js +43 -2
- package/dist_ts/events/events.observable.d.ts +15 -0
- package/dist_ts/events/events.observable.js +22 -0
- package/dist_ts/events/index.d.ts +1 -0
- package/dist_ts/events/index.js +2 -0
- package/dist_ts/factories/index.js +7 -5
- package/dist_ts/features/feature.abstract.d.ts +2 -2
- package/dist_ts/features/feature.abstract.js +3 -3
- package/dist_ts/features/feature.print.d.ts +3 -0
- package/dist_ts/features/feature.print.js +17 -2
- package/dist_ts/features/feature.scan.d.ts +5 -2
- package/dist_ts/features/feature.scan.js +98 -27
- package/dist_ts/index.d.ts +3 -1
- package/dist_ts/index.js +4 -2
- package/dist_ts/interfaces/feature.interfaces.d.ts +21 -1
- package/dist_ts/interfaces/index.d.ts +21 -3
- package/dist_ts/interfaces/index.js +1 -1
- package/dist_ts/plugins.d.ts +2 -1
- package/dist_ts/plugins.js +3 -2
- package/dist_ts/protocols/index.d.ts +2 -1
- package/dist_ts/protocols/index.js +4 -2
- package/dist_ts/protocols/protocol.brother.d.ts +67 -0
- package/dist_ts/protocols/protocol.brother.js +560 -0
- package/dist_ts/protocols/protocol.escl.d.ts +13 -4
- package/dist_ts/protocols/protocol.escl.js +344 -177
- package/dist_ts/protocols/protocol.ipp.d.ts +2 -0
- package/dist_ts/protocols/protocol.ipp.js +22 -2
- package/dist_ts/providers/index.d.ts +1 -0
- package/dist_ts/providers/index.js +2 -0
- package/dist_ts/providers/provider.smb.d.ts +60 -0
- package/dist_ts/providers/provider.smb.js +224 -0
- package/package.json +11 -7
- package/pnpm-workspace.yaml +2 -0
- package/readme.md +98 -4
- package/test/test.brother.node.ts +306 -0
- package/test/test.escl.node.ts +233 -0
- package/test/test.ipp.node.ts +75 -0
- package/test/test.node.ts +17 -0
- package/test/test.smb.node.ts +123 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/device/device.classes.device.ts +2 -2
- package/ts/devicemanager.classes.devicemanager.ts +81 -6
- package/ts/discovery/discovery.classes.networkscanner.ts +50 -1
- package/ts/events/events.observable.ts +31 -0
- package/ts/events/index.ts +4 -0
- package/ts/factories/index.ts +7 -5
- package/ts/features/feature.abstract.ts +2 -2
- package/ts/features/feature.print.ts +16 -1
- package/ts/features/feature.scan.ts +93 -27
- package/ts/index.ts +20 -0
- package/ts/interfaces/feature.interfaces.ts +22 -1
- package/ts/interfaces/index.ts +22 -3
- package/ts/plugins.ts +2 -1
- package/ts/protocols/index.ts +11 -1
- package/ts/protocols/protocol.brother.ts +648 -0
- package/ts/protocols/protocol.escl.ts +347 -190
- package/ts/protocols/protocol.ipp.ts +24 -1
- package/ts/providers/index.ts +7 -0
- package/ts/providers/provider.smb.ts +326 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as plugins from '../plugins.js';
|
|
2
|
-
import { EsclProtocol } from '../protocols/index.js';
|
|
2
|
+
import { BrotherScanProtocol, EsclProtocol } from '../protocols/index.js';
|
|
3
3
|
import {
|
|
4
4
|
cidrToIps,
|
|
5
5
|
ipRangeToIps,
|
|
@@ -19,6 +19,7 @@ const DEFAULT_PORTS = [
|
|
|
19
19
|
631, // IPP printers
|
|
20
20
|
80, // eSCL scanners (HTTP)
|
|
21
21
|
443, // eSCL scanners (HTTPS)
|
|
22
|
+
54921, // Brother native network scanners
|
|
22
23
|
6566, // SANE scanners
|
|
23
24
|
9100, // JetDirect printers
|
|
24
25
|
7000, // AirPlay speakers
|
|
@@ -35,6 +36,7 @@ const DEFAULT_OPTIONS: Required<Omit<INetworkScanOptions, 'ipRange' | 'startIp'
|
|
|
35
36
|
concurrency: 50,
|
|
36
37
|
timeout: 2000,
|
|
37
38
|
ports: DEFAULT_PORTS,
|
|
39
|
+
probeBrother: true,
|
|
38
40
|
probeEscl: true,
|
|
39
41
|
probeIpp: true,
|
|
40
42
|
probeSane: true,
|
|
@@ -205,6 +207,15 @@ export class NetworkScanner extends plugins.events.EventEmitter {
|
|
|
205
207
|
const probePromises: Promise<void>[] = [];
|
|
206
208
|
|
|
207
209
|
for (const port of openPorts) {
|
|
210
|
+
// Brother native scan service
|
|
211
|
+
if (opts.probeBrother && port === 54921) {
|
|
212
|
+
probePromises.push(
|
|
213
|
+
this.probeBrother(ip, port, timeout).then((device) => {
|
|
214
|
+
if (device) devices.push(device);
|
|
215
|
+
})
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
208
219
|
// IPP probe (port 631)
|
|
209
220
|
if (opts.probeIpp && port === 631) {
|
|
210
221
|
probePromises.push(
|
|
@@ -282,6 +293,17 @@ export class NetworkScanner extends plugins.events.EventEmitter {
|
|
|
282
293
|
|
|
283
294
|
await Promise.all(probePromises);
|
|
284
295
|
|
|
296
|
+
// Prefer a vendor transport that enforces scan settings over standards
|
|
297
|
+
// endpoints whose firmware may silently downgrade those settings.
|
|
298
|
+
const protocolPriority: Partial<Record<INetworkScanDevice['protocol'], number>> = {
|
|
299
|
+
brother: 0,
|
|
300
|
+
escl: 1,
|
|
301
|
+
sane: 2,
|
|
302
|
+
};
|
|
303
|
+
devices.sort((left, right) =>
|
|
304
|
+
(protocolPriority[left.protocol] ?? 10) - (protocolPriority[right.protocol] ?? 10)
|
|
305
|
+
);
|
|
306
|
+
|
|
285
307
|
return devices;
|
|
286
308
|
}
|
|
287
309
|
|
|
@@ -330,6 +352,33 @@ export class NetworkScanner extends plugins.events.EventEmitter {
|
|
|
330
352
|
});
|
|
331
353
|
}
|
|
332
354
|
|
|
355
|
+
/**
|
|
356
|
+
* Probe for Brother's native network scan service.
|
|
357
|
+
*/
|
|
358
|
+
private async probeBrother(
|
|
359
|
+
ip: string,
|
|
360
|
+
port: number,
|
|
361
|
+
timeout: number
|
|
362
|
+
): Promise<INetworkScanDevice | null> {
|
|
363
|
+
const brother = new BrotherScanProtocol(ip, port, { timeout, busyTimeout: 0 });
|
|
364
|
+
try {
|
|
365
|
+
await brother.connect();
|
|
366
|
+
return {
|
|
367
|
+
type: 'scanner',
|
|
368
|
+
protocol: 'brother',
|
|
369
|
+
port,
|
|
370
|
+
name: `Brother Scanner at ${ip}`,
|
|
371
|
+
model: undefined,
|
|
372
|
+
};
|
|
373
|
+
} catch {
|
|
374
|
+
// A busy endpoint is not selected because connection would fail; eSCL
|
|
375
|
+
// can still be discovered as a fallback on the same device.
|
|
376
|
+
return null;
|
|
377
|
+
} finally {
|
|
378
|
+
await brother.disconnect();
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
333
382
|
/**
|
|
334
383
|
* Probe for IPP printer using a simple HTTP check
|
|
335
384
|
* Use a minimal request instead of an external IPP client for discovery probing.
|
|
@@ -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
|
+
}
|
package/ts/factories/index.ts
CHANGED
|
@@ -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 =
|
|
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
|
|
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:
|
|
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
|
|
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 === '
|
|
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 === '
|
|
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 === '
|
|
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 ??
|
|
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
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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,
|
|
@@ -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[];
|
|
@@ -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
|
|
package/ts/interfaces/index.ts
CHANGED
|
@@ -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 {
|
|
@@ -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 (
|
|
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
|
@@ -17,5 +17,6 @@ export { smartpath, smartdelay };
|
|
|
17
17
|
import * as bonjourService from 'bonjour-service';
|
|
18
18
|
import * as netSnmp from 'net-snmp';
|
|
19
19
|
import WebSocket from 'ws';
|
|
20
|
+
import * as rxjs from 'rxjs';
|
|
20
21
|
|
|
21
|
-
export { bonjourService, netSnmp, WebSocket };
|
|
22
|
+
export { bonjourService, netSnmp, WebSocket, rxjs };
|
package/ts/protocols/index.ts
CHANGED
|
@@ -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';
|