@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.
- package/.gitea/workflows/default_tags.yaml +0 -21
- package/.smartconfig.json +69 -0
- package/changelog.md +38 -1
- 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 +99 -11
- package/dist_ts/discovery/discovery.classes.mdns.js +46 -11
- package/dist_ts/discovery/discovery.classes.networkscanner.d.ts +5 -1
- package/dist_ts/discovery/discovery.classes.networkscanner.js +44 -3
- package/dist_ts/discovery/discovery.classes.ssdp.d.ts +52 -21
- package/dist_ts/discovery/discovery.classes.ssdp.js +407 -117
- 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 +4 -2
- package/dist_ts/index.js +4 -2
- package/dist_ts/interfaces/feature.interfaces.d.ts +22 -2
- package/dist_ts/interfaces/index.d.ts +22 -4
- package/dist_ts/interfaces/index.js +1 -1
- package/dist_ts/plugins.d.ts +6 -15
- package/dist_ts/plugins.js +7 -19
- 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 +345 -178
- 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/license.md +21 -0
- package/package.json +15 -20
- package/pnpm-workspace.yaml +6 -0
- package/readme.md +157 -78
- 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.ts → test.node.ts} +37 -45
- package/test/test.smb.node.ts +123 -0
- package/test/test.ssdp.node.ts +978 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/device/device.classes.device.ts +2 -2
- package/ts/devicemanager.classes.devicemanager.ts +125 -12
- package/ts/discovery/discovery.classes.mdns.ts +40 -11
- package/ts/discovery/discovery.classes.networkscanner.ts +51 -2
- package/ts/discovery/discovery.classes.ssdp.ts +493 -130
- 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 +21 -0
- package/ts/interfaces/feature.interfaces.ts +23 -2
- package/ts/interfaces/index.ts +23 -4
- package/ts/plugins.ts +6 -25
- package/ts/protocols/index.ts +11 -1
- package/ts/protocols/protocol.brother.ts +648 -0
- package/ts/protocols/protocol.escl.ts +348 -191
- package/ts/protocols/protocol.ipp.ts +24 -1
- package/ts/providers/index.ts +7 -0
- package/ts/providers/provider.smb.ts +326 -0
- package/dist_ts/protocols/protocol.ipp.old.d.ts +0 -45
- package/dist_ts/protocols/protocol.ipp.old.js +0 -284
- package/npmextra.json +0 -24
|
@@ -32,6 +32,7 @@ tap.test('should export device factories', async () => {
|
|
|
32
32
|
|
|
33
33
|
tap.test('should export protocol implementations', async () => {
|
|
34
34
|
expect(devicemanager.EsclProtocol).toBeDefined();
|
|
35
|
+
expect(devicemanager.BrotherScanProtocol).toBeDefined();
|
|
35
36
|
expect(devicemanager.SaneProtocol).toBeDefined();
|
|
36
37
|
expect(devicemanager.IppProtocol).toBeDefined();
|
|
37
38
|
expect(devicemanager.SnmpProtocol).toBeDefined();
|
|
@@ -127,50 +128,6 @@ tap.test('withRetry should throw after max retries', async () => {
|
|
|
127
128
|
expect(callCount).toEqual(3); // Initial + 2 retries
|
|
128
129
|
});
|
|
129
130
|
|
|
130
|
-
// Test discovery (non-blocking)
|
|
131
|
-
tap.test('should start and stop discovery', async () => {
|
|
132
|
-
const dm = new devicemanager.DeviceManager();
|
|
133
|
-
|
|
134
|
-
// Track events
|
|
135
|
-
let discoveryStarted = false;
|
|
136
|
-
let discoveryStopped = false;
|
|
137
|
-
|
|
138
|
-
dm.on('discovery:started', () => {
|
|
139
|
-
discoveryStarted = true;
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
dm.on('discovery:stopped', () => {
|
|
143
|
-
discoveryStopped = true;
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
await dm.startDiscovery();
|
|
147
|
-
expect(dm.isDiscovering).toEqual(true);
|
|
148
|
-
expect(discoveryStarted).toEqual(true);
|
|
149
|
-
|
|
150
|
-
// Wait a bit for potential device discovery
|
|
151
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
152
|
-
|
|
153
|
-
// Log discovered devices using new API
|
|
154
|
-
const scanners = dm.getDevices({ hasFeature: 'scan' });
|
|
155
|
-
const printers = dm.getDevices({ hasFeature: 'print' });
|
|
156
|
-
console.log(`Discovered ${scanners.length} scanner(s) and ${printers.length} printer(s)`);
|
|
157
|
-
|
|
158
|
-
for (const scanner of scanners) {
|
|
159
|
-
const scanFeature = scanner.getFeature<devicemanager.ScanFeature>('scan');
|
|
160
|
-
console.log(` Scanner: ${scanner.name} (${scanner.address}) - ${scanFeature?.protocol || 'unknown'}`);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
for (const printer of printers) {
|
|
164
|
-
console.log(` Printer: ${printer.name} (${printer.address})`);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
await dm.stopDiscovery();
|
|
168
|
-
expect(dm.isDiscovering).toEqual(false);
|
|
169
|
-
expect(discoveryStopped).toEqual(true);
|
|
170
|
-
|
|
171
|
-
await dm.shutdown();
|
|
172
|
-
});
|
|
173
|
-
|
|
174
131
|
// Test Scanner creation using factory
|
|
175
132
|
tap.test('should create Scanner device using factory', async () => {
|
|
176
133
|
const scanner = devicemanager.createScanner({
|
|
@@ -197,6 +154,22 @@ tap.test('should create Scanner device using factory', async () => {
|
|
|
197
154
|
expect(scanFeature.protocol).toEqual('escl');
|
|
198
155
|
});
|
|
199
156
|
|
|
157
|
+
tap.test('duplex-capable scanners default to duplex ADF', async () => {
|
|
158
|
+
const scanner = devicemanager.createScanner({
|
|
159
|
+
id: 'test:brother-scanner:1',
|
|
160
|
+
name: 'Brother Scanner',
|
|
161
|
+
address: '192.168.1.101',
|
|
162
|
+
port: 54921,
|
|
163
|
+
protocol: 'brother',
|
|
164
|
+
txtRecords: {},
|
|
165
|
+
});
|
|
166
|
+
const scanFeature = scanner.selectFeature<devicemanager.ScanFeature>('scan');
|
|
167
|
+
const defaults = (scanFeature as unknown as {
|
|
168
|
+
resolveOptions(options?: devicemanager.IScanOptions): Required<devicemanager.IScanOptions>;
|
|
169
|
+
}).resolveOptions();
|
|
170
|
+
expect(defaults.source).toEqual('adf-duplex');
|
|
171
|
+
});
|
|
172
|
+
|
|
200
173
|
// Test Printer creation using factory
|
|
201
174
|
tap.test('should create Printer device using factory', async () => {
|
|
202
175
|
const printer = devicemanager.createPrinter({
|
|
@@ -204,8 +177,8 @@ tap.test('should create Printer device using factory', async () => {
|
|
|
204
177
|
name: 'Test Printer',
|
|
205
178
|
address: '192.168.1.101',
|
|
206
179
|
port: 631,
|
|
207
|
-
ippPath: '/ipp/print',
|
|
208
180
|
txtRecords: {
|
|
181
|
+
'rp': '/ipp/print',
|
|
209
182
|
'ty': 'Brother HL-L2350DW',
|
|
210
183
|
'Color': 'T',
|
|
211
184
|
'Duplex': 'T',
|
|
@@ -221,6 +194,25 @@ tap.test('should create Printer device using factory', async () => {
|
|
|
221
194
|
expect(printFeature).toBeInstanceOf(devicemanager.PrintFeature);
|
|
222
195
|
});
|
|
223
196
|
|
|
197
|
+
tap.test('should create a Sonos speaker without an external Sonos client', async () => {
|
|
198
|
+
const speaker = devicemanager.createSpeaker({
|
|
199
|
+
id: 'test:speaker:sonos',
|
|
200
|
+
name: 'Living Room',
|
|
201
|
+
address: '192.168.1.102',
|
|
202
|
+
port: 1400,
|
|
203
|
+
protocol: 'sonos',
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const playback = speaker.selectFeature<devicemanager.PlaybackFeature>('playback');
|
|
207
|
+
const volume = speaker.selectFeature<devicemanager.VolumeFeature>('volume');
|
|
208
|
+
|
|
209
|
+
expect(speaker.id).toEqual('test:speaker:sonos');
|
|
210
|
+
expect(playback.protocol).toEqual('sonos');
|
|
211
|
+
expect(playback.supportsQueue).toEqual(true);
|
|
212
|
+
expect(playback.supportsSeek).toEqual(true);
|
|
213
|
+
expect(volume.protocol).toEqual('sonos');
|
|
214
|
+
});
|
|
215
|
+
|
|
224
216
|
// Test UniversalDevice feature management
|
|
225
217
|
tap.test('should manage features on UniversalDevice', async () => {
|
|
226
218
|
const device = new devicemanager.UniversalDevice('192.168.1.50', 80, {
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
|
2
|
+
import * as fs from 'node:fs/promises';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { filter, firstValueFrom, take, timeout, toArray } from 'rxjs';
|
|
6
|
+
import {
|
|
7
|
+
DeviceManager,
|
|
8
|
+
type ISmbServerAdapter,
|
|
9
|
+
type ISmbShareProviderOptions,
|
|
10
|
+
} from '../ts/index.js';
|
|
11
|
+
|
|
12
|
+
class FakeSmbServer implements ISmbServerAdapter {
|
|
13
|
+
public running = false;
|
|
14
|
+
|
|
15
|
+
constructor(private readonly options: ISmbShareProviderOptions) {}
|
|
16
|
+
|
|
17
|
+
public async start() {
|
|
18
|
+
this.running = true;
|
|
19
|
+
return {
|
|
20
|
+
host: this.options.host ?? '127.0.0.1',
|
|
21
|
+
port: this.options.port ?? 445,
|
|
22
|
+
address: `smb://${this.options.host ?? '127.0.0.1'}:${this.options.port ?? 445}`,
|
|
23
|
+
shares: this.options.shares.map((share) => share.name),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public async stop(): Promise<void> {
|
|
28
|
+
this.running = false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public async status() {
|
|
32
|
+
return {
|
|
33
|
+
running: this.running,
|
|
34
|
+
host: this.options.host,
|
|
35
|
+
port: this.options.port,
|
|
36
|
+
shares: this.options.shares.map((share) => share.name),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public getConnectionOptions() {
|
|
41
|
+
return {
|
|
42
|
+
host: this.options.host ?? '127.0.0.1',
|
|
43
|
+
port: this.options.port ?? 445,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
tap.test('SMB provider emits settled documents through provider and manager RxJS streams', async () => {
|
|
49
|
+
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'devicemanager-smb-'));
|
|
50
|
+
const sharePath = path.join(tempRoot, 'scans');
|
|
51
|
+
const manager = new DeviceManager({ autoDiscovery: false });
|
|
52
|
+
const options: ISmbShareProviderOptions = {
|
|
53
|
+
host: '127.0.0.1',
|
|
54
|
+
port: 1445,
|
|
55
|
+
shares: [{ name: 'brother-scans', path: sharePath }],
|
|
56
|
+
pollIntervalMs: 20,
|
|
57
|
+
settleTimeMs: 60,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const provider = await manager.provideSmbShares(options, {
|
|
61
|
+
serverFactory: (serverOptions) => new FakeSmbServer({ ...options, ...serverOptions }),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const providerEventPromise = firstValueFrom(provider.documents$.pipe(timeout({ first: 3000 })));
|
|
66
|
+
const managerEventPromise = firstValueFrom(manager.events$.pipe(
|
|
67
|
+
filter((event) => event.name === 'smb:document:arrived'),
|
|
68
|
+
timeout({ first: 3000 })
|
|
69
|
+
));
|
|
70
|
+
|
|
71
|
+
const documentPath = path.join(sharePath, 'nested', 'scan.pdf');
|
|
72
|
+
await fs.mkdir(path.dirname(documentPath), { recursive: true });
|
|
73
|
+
await fs.writeFile(documentPath, 'first');
|
|
74
|
+
await new Promise((resolve) => setTimeout(resolve, 35));
|
|
75
|
+
await fs.appendFile(documentPath, '-complete');
|
|
76
|
+
|
|
77
|
+
const providerEvent = await providerEventPromise;
|
|
78
|
+
const managerEvent = await managerEventPromise;
|
|
79
|
+
expect(providerEvent.shareName).toEqual('brother-scans');
|
|
80
|
+
expect(providerEvent.relativePath).toEqual('nested/scan.pdf');
|
|
81
|
+
expect(providerEvent.size).toEqual('first-complete'.length);
|
|
82
|
+
expect((managerEvent.args[0] as { absolutePath: string }).absolutePath).toEqual(documentPath);
|
|
83
|
+
} finally {
|
|
84
|
+
await manager.stopSmbShareProvider(provider);
|
|
85
|
+
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
tap.test('SMB provider keeps document events distinct for shares with the same path', async () => {
|
|
90
|
+
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'devicemanager-smb-shares-'));
|
|
91
|
+
const manager = new DeviceManager({ autoDiscovery: false });
|
|
92
|
+
const options: ISmbShareProviderOptions = {
|
|
93
|
+
host: '127.0.0.1',
|
|
94
|
+
port: 1445,
|
|
95
|
+
shares: [
|
|
96
|
+
{ name: 'scanner-a', path: tempRoot },
|
|
97
|
+
{ name: 'scanner-b', path: tempRoot },
|
|
98
|
+
],
|
|
99
|
+
pollIntervalMs: 20,
|
|
100
|
+
settleTimeMs: 40,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const provider = await manager.provideSmbShares(options, {
|
|
104
|
+
serverFactory: (serverOptions) => new FakeSmbServer({ ...options, ...serverOptions }),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const eventsPromise = firstValueFrom(provider.documents$.pipe(
|
|
109
|
+
take(2),
|
|
110
|
+
toArray(),
|
|
111
|
+
timeout({ first: 3000 })
|
|
112
|
+
));
|
|
113
|
+
|
|
114
|
+
await fs.writeFile(path.join(tempRoot, 'scan.pdf'), 'complete');
|
|
115
|
+
const events = await eventsPromise;
|
|
116
|
+
expect(events.map((event) => event.shareName).sort()).toEqual(['scanner-a', 'scanner-b']);
|
|
117
|
+
} finally {
|
|
118
|
+
await manager.stopSmbShareProvider(provider);
|
|
119
|
+
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
export default tap.start();
|