@ecobridge.xyz/devicemanager 3.0.2
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_nottags.yaml +66 -0
- package/.gitea/workflows/default_tags.yaml +124 -0
- package/.vscode/launch.json +11 -0
- package/.vscode/settings.json +26 -0
- package/changelog.md +91 -0
- package/dist_ts/00_commitinfo_data.d.ts +8 -0
- package/dist_ts/00_commitinfo_data.js +9 -0
- package/dist_ts/device/device.classes.device.d.ts +205 -0
- package/dist_ts/device/device.classes.device.js +344 -0
- package/dist_ts/devicemanager.classes.devicemanager.d.ts +210 -0
- package/dist_ts/devicemanager.classes.devicemanager.js +835 -0
- package/dist_ts/discovery/discovery.classes.homeassistant.d.ts +101 -0
- package/dist_ts/discovery/discovery.classes.homeassistant.js +313 -0
- package/dist_ts/discovery/discovery.classes.mdns.d.ts +90 -0
- package/dist_ts/discovery/discovery.classes.mdns.js +291 -0
- package/dist_ts/discovery/discovery.classes.networkscanner.d.ts +64 -0
- package/dist_ts/discovery/discovery.classes.networkscanner.js +535 -0
- package/dist_ts/discovery/discovery.classes.ssdp.d.ts +112 -0
- package/dist_ts/discovery/discovery.classes.ssdp.js +255 -0
- package/dist_ts/factories/index.d.ts +190 -0
- package/dist_ts/factories/index.js +350 -0
- package/dist_ts/features/feature.abstract.d.ts +118 -0
- package/dist_ts/features/feature.abstract.js +172 -0
- package/dist_ts/features/feature.camera.d.ts +95 -0
- package/dist_ts/features/feature.camera.js +162 -0
- package/dist_ts/features/feature.climate.d.ts +174 -0
- package/dist_ts/features/feature.climate.js +328 -0
- package/dist_ts/features/feature.cover.d.ts +121 -0
- package/dist_ts/features/feature.cover.js +217 -0
- package/dist_ts/features/feature.fan.d.ts +127 -0
- package/dist_ts/features/feature.fan.js +235 -0
- package/dist_ts/features/feature.light.d.ts +156 -0
- package/dist_ts/features/feature.light.js +296 -0
- package/dist_ts/features/feature.lock.d.ts +103 -0
- package/dist_ts/features/feature.lock.js +170 -0
- package/dist_ts/features/feature.playback.d.ts +98 -0
- package/dist_ts/features/feature.playback.js +191 -0
- package/dist_ts/features/feature.power.d.ts +79 -0
- package/dist_ts/features/feature.power.js +173 -0
- package/dist_ts/features/feature.print.d.ts +78 -0
- package/dist_ts/features/feature.print.js +226 -0
- package/dist_ts/features/feature.scan.d.ts +79 -0
- package/dist_ts/features/feature.scan.js +315 -0
- package/dist_ts/features/feature.sensor.d.ts +95 -0
- package/dist_ts/features/feature.sensor.js +147 -0
- package/dist_ts/features/feature.snmp.d.ts +88 -0
- package/dist_ts/features/feature.snmp.js +175 -0
- package/dist_ts/features/feature.switch.d.ts +75 -0
- package/dist_ts/features/feature.switch.js +126 -0
- package/dist_ts/features/feature.volume.d.ts +104 -0
- package/dist_ts/features/feature.volume.js +186 -0
- package/dist_ts/features/index.d.ts +19 -0
- package/dist_ts/features/index.js +23 -0
- package/dist_ts/helpers/helpers.iprange.d.ts +35 -0
- package/dist_ts/helpers/helpers.iprange.js +151 -0
- package/dist_ts/helpers/helpers.retry.d.ts +25 -0
- package/dist_ts/helpers/helpers.retry.js +69 -0
- package/dist_ts/index.d.ts +15 -0
- package/dist_ts/index.js +40 -0
- package/dist_ts/interfaces/feature.interfaces.d.ts +255 -0
- package/dist_ts/interfaces/feature.interfaces.js +6 -0
- package/dist_ts/interfaces/homeassistant.interfaces.d.ts +550 -0
- package/dist_ts/interfaces/homeassistant.interfaces.js +111 -0
- package/dist_ts/interfaces/index.d.ts +330 -0
- package/dist_ts/interfaces/index.js +16 -0
- package/dist_ts/interfaces/smarthome.interfaces.d.ts +282 -0
- package/dist_ts/interfaces/smarthome.interfaces.js +7 -0
- package/dist_ts/paths.d.ts +1 -0
- package/dist_ts/paths.js +3 -0
- package/dist_ts/plugins.d.ts +23 -0
- package/dist_ts/plugins.js +30 -0
- package/dist_ts/protocols/index.d.ts +12 -0
- package/dist_ts/protocols/index.js +21 -0
- package/dist_ts/protocols/protocol.escl.d.ts +58 -0
- package/dist_ts/protocols/protocol.escl.js +364 -0
- package/dist_ts/protocols/protocol.homeassistant.d.ts +248 -0
- package/dist_ts/protocols/protocol.homeassistant.js +611 -0
- package/dist_ts/protocols/protocol.ipp.d.ts +45 -0
- package/dist_ts/protocols/protocol.ipp.js +284 -0
- package/dist_ts/protocols/protocol.nut.d.ts +160 -0
- package/dist_ts/protocols/protocol.nut.js +364 -0
- package/dist_ts/protocols/protocol.sane.d.ts +104 -0
- package/dist_ts/protocols/protocol.sane.js +597 -0
- package/dist_ts/protocols/protocol.snmp.d.ts +133 -0
- package/dist_ts/protocols/protocol.snmp.js +333 -0
- package/dist_ts/protocols/protocol.upnp.d.ts +207 -0
- package/dist_ts/protocols/protocol.upnp.js +442 -0
- package/dist_ts/protocols/protocol.upssnmp.d.ts +160 -0
- package/dist_ts/protocols/protocol.upssnmp.js +263 -0
- package/npmextra.json +24 -0
- package/package.json +39 -0
- package/readme.hints.md +115 -0
- package/readme.md +624 -0
- package/test/test.ts +261 -0
- package/ts/00_commitinfo_data.ts +8 -0
- package/ts/device/device.classes.device.ts +454 -0
- package/ts/devicemanager.classes.devicemanager.ts +1219 -0
- package/ts/discovery/discovery.classes.homeassistant.ts +376 -0
- package/ts/discovery/discovery.classes.mdns.ts +347 -0
- package/ts/discovery/discovery.classes.networkscanner.ts +648 -0
- package/ts/discovery/discovery.classes.ssdp.ts +357 -0
- package/ts/factories/index.ts +696 -0
- package/ts/features/feature.abstract.ts +251 -0
- package/ts/features/feature.camera.ts +214 -0
- package/ts/features/feature.climate.ts +407 -0
- package/ts/features/feature.cover.ts +278 -0
- package/ts/features/feature.fan.ts +296 -0
- package/ts/features/feature.light.ts +369 -0
- package/ts/features/feature.lock.ts +223 -0
- package/ts/features/feature.playback.ts +246 -0
- package/ts/features/feature.power.ts +231 -0
- package/ts/features/feature.print.ts +297 -0
- package/ts/features/feature.scan.ts +370 -0
- package/ts/features/feature.sensor.ts +202 -0
- package/ts/features/feature.snmp.ts +235 -0
- package/ts/features/feature.switch.ts +170 -0
- package/ts/features/feature.volume.ts +256 -0
- package/ts/features/index.ts +25 -0
- package/ts/helpers/helpers.iprange.ts +172 -0
- package/ts/helpers/helpers.retry.ts +100 -0
- package/ts/index.ts +165 -0
- package/ts/interfaces/feature.interfaces.ts +359 -0
- package/ts/interfaces/homeassistant.interfaces.ts +666 -0
- package/ts/interfaces/index.ts +421 -0
- package/ts/interfaces/smarthome.interfaces.ts +421 -0
- package/ts/paths.ts +5 -0
- package/ts/plugins.ts +41 -0
- package/ts/protocols/index.ts +59 -0
- package/ts/protocols/protocol.escl.ts +439 -0
- package/ts/protocols/protocol.homeassistant.ts +737 -0
- package/ts/protocols/protocol.ipp.ts +329 -0
- package/ts/protocols/protocol.nut.ts +471 -0
- package/ts/protocols/protocol.sane.ts +694 -0
- package/ts/protocols/protocol.snmp.ts +439 -0
- package/ts/protocols/protocol.upnp.ts +627 -0
- package/ts/protocols/protocol.upssnmp.ts +377 -0
- package/tsconfig.json +12 -0
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SSDP service types for device discovery
|
|
5
|
+
*/
|
|
6
|
+
export const SSDP_SERVICE_TYPES = {
|
|
7
|
+
// Root device
|
|
8
|
+
ROOT: 'upnp:rootdevice',
|
|
9
|
+
|
|
10
|
+
// DLNA/UPnP AV
|
|
11
|
+
MEDIA_RENDERER: 'urn:schemas-upnp-org:device:MediaRenderer:1',
|
|
12
|
+
MEDIA_SERVER: 'urn:schemas-upnp-org:device:MediaServer:1',
|
|
13
|
+
|
|
14
|
+
// Sonos
|
|
15
|
+
SONOS_ZONE_PLAYER: 'urn:schemas-upnp-org:device:ZonePlayer:1',
|
|
16
|
+
|
|
17
|
+
// Generic
|
|
18
|
+
BASIC_DEVICE: 'urn:schemas-upnp-org:device:Basic:1',
|
|
19
|
+
INTERNET_GATEWAY: 'urn:schemas-upnp-org:device:InternetGatewayDevice:1',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* SSDP discovered device information
|
|
24
|
+
*/
|
|
25
|
+
export interface ISsdpDevice {
|
|
26
|
+
/** Unique service name (USN) */
|
|
27
|
+
usn: string;
|
|
28
|
+
/** Service type (ST) */
|
|
29
|
+
serviceType: string;
|
|
30
|
+
/** Location URL of device description */
|
|
31
|
+
location: string;
|
|
32
|
+
/** IP address */
|
|
33
|
+
address: string;
|
|
34
|
+
/** Port number */
|
|
35
|
+
port: number;
|
|
36
|
+
/** Device description (fetched from location) */
|
|
37
|
+
description?: ISsdpDeviceDescription;
|
|
38
|
+
/** Raw headers from SSDP response */
|
|
39
|
+
headers: Record<string, string>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* UPnP device description (from XML)
|
|
44
|
+
*/
|
|
45
|
+
export interface ISsdpDeviceDescription {
|
|
46
|
+
deviceType: string;
|
|
47
|
+
friendlyName: string;
|
|
48
|
+
manufacturer: string;
|
|
49
|
+
manufacturerURL?: string;
|
|
50
|
+
modelDescription?: string;
|
|
51
|
+
modelName: string;
|
|
52
|
+
modelNumber?: string;
|
|
53
|
+
modelURL?: string;
|
|
54
|
+
serialNumber?: string;
|
|
55
|
+
UDN: string;
|
|
56
|
+
services: ISsdpService[];
|
|
57
|
+
icons?: ISsdpIcon[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface ISsdpService {
|
|
61
|
+
serviceType: string;
|
|
62
|
+
serviceId: string;
|
|
63
|
+
SCPDURL: string;
|
|
64
|
+
controlURL: string;
|
|
65
|
+
eventSubURL: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface ISsdpIcon {
|
|
69
|
+
mimetype: string;
|
|
70
|
+
width: number;
|
|
71
|
+
height: number;
|
|
72
|
+
depth: number;
|
|
73
|
+
url: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* SSDP Discovery service using node-ssdp
|
|
78
|
+
*/
|
|
79
|
+
export class SsdpDiscovery extends plugins.events.EventEmitter {
|
|
80
|
+
private client: InstanceType<typeof plugins.nodeSsdp.Client> | null = null;
|
|
81
|
+
private devices: Map<string, ISsdpDevice> = new Map();
|
|
82
|
+
private running = false;
|
|
83
|
+
private searchInterval: NodeJS.Timeout | null = null;
|
|
84
|
+
|
|
85
|
+
constructor() {
|
|
86
|
+
super();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Start SSDP discovery
|
|
91
|
+
*/
|
|
92
|
+
public async start(serviceTypes?: string[]): Promise<void> {
|
|
93
|
+
if (this.running) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
this.running = true;
|
|
98
|
+
this.client = new plugins.nodeSsdp.Client();
|
|
99
|
+
|
|
100
|
+
// Handle SSDP responses
|
|
101
|
+
this.client.on('response', (headers: Record<string, string>, statusCode: number, rinfo: { address: string; port: number }) => {
|
|
102
|
+
this.handleSsdpResponse(headers, rinfo);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Search for devices
|
|
106
|
+
const typesToSearch = serviceTypes ?? [
|
|
107
|
+
SSDP_SERVICE_TYPES.ROOT,
|
|
108
|
+
SSDP_SERVICE_TYPES.MEDIA_RENDERER,
|
|
109
|
+
SSDP_SERVICE_TYPES.MEDIA_SERVER,
|
|
110
|
+
SSDP_SERVICE_TYPES.SONOS_ZONE_PLAYER,
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
// Initial search
|
|
114
|
+
for (const st of typesToSearch) {
|
|
115
|
+
this.client.search(st);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Periodic re-search (every 30 seconds)
|
|
119
|
+
this.searchInterval = setInterval(() => {
|
|
120
|
+
if (this.client) {
|
|
121
|
+
for (const st of typesToSearch) {
|
|
122
|
+
this.client.search(st);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}, 30000);
|
|
126
|
+
|
|
127
|
+
this.emit('started');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Stop SSDP discovery
|
|
132
|
+
*/
|
|
133
|
+
public async stop(): Promise<void> {
|
|
134
|
+
if (!this.running) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
this.running = false;
|
|
139
|
+
|
|
140
|
+
if (this.searchInterval) {
|
|
141
|
+
clearInterval(this.searchInterval);
|
|
142
|
+
this.searchInterval = null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (this.client) {
|
|
146
|
+
this.client.stop();
|
|
147
|
+
this.client = null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
this.emit('stopped');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Check if discovery is running
|
|
155
|
+
*/
|
|
156
|
+
public get isRunning(): boolean {
|
|
157
|
+
return this.running;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Get all discovered devices
|
|
162
|
+
*/
|
|
163
|
+
public getDevices(): ISsdpDevice[] {
|
|
164
|
+
return Array.from(this.devices.values());
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get devices by service type
|
|
169
|
+
*/
|
|
170
|
+
public getDevicesByType(serviceType: string): ISsdpDevice[] {
|
|
171
|
+
return this.getDevices().filter((d) => d.serviceType === serviceType);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Search for a specific service type
|
|
176
|
+
*/
|
|
177
|
+
public search(serviceType: string): void {
|
|
178
|
+
if (this.client && this.running) {
|
|
179
|
+
this.client.search(serviceType);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Handle SSDP response
|
|
185
|
+
*/
|
|
186
|
+
private handleSsdpResponse(
|
|
187
|
+
headers: Record<string, string>,
|
|
188
|
+
rinfo: { address: string; port: number }
|
|
189
|
+
): void {
|
|
190
|
+
const usn = headers['USN'] || headers['usn'];
|
|
191
|
+
const location = headers['LOCATION'] || headers['location'];
|
|
192
|
+
const st = headers['ST'] || headers['st'];
|
|
193
|
+
|
|
194
|
+
if (!usn || !location) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Parse location URL
|
|
199
|
+
let address = rinfo.address;
|
|
200
|
+
let port = 80;
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
const url = new URL(location);
|
|
204
|
+
address = url.hostname;
|
|
205
|
+
port = parseInt(url.port) || (url.protocol === 'https:' ? 443 : 80);
|
|
206
|
+
} catch {
|
|
207
|
+
// Keep rinfo address
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const device: ISsdpDevice = {
|
|
211
|
+
usn,
|
|
212
|
+
serviceType: st || 'unknown',
|
|
213
|
+
location,
|
|
214
|
+
address,
|
|
215
|
+
port,
|
|
216
|
+
headers: { ...headers },
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const isNew = !this.devices.has(usn);
|
|
220
|
+
this.devices.set(usn, device);
|
|
221
|
+
|
|
222
|
+
if (isNew) {
|
|
223
|
+
// Fetch device description
|
|
224
|
+
this.fetchDeviceDescription(device).then(() => {
|
|
225
|
+
this.emit('device:found', device);
|
|
226
|
+
}).catch(() => {
|
|
227
|
+
// Still emit even without description
|
|
228
|
+
this.emit('device:found', device);
|
|
229
|
+
});
|
|
230
|
+
} else {
|
|
231
|
+
this.emit('device:updated', device);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Fetch and parse device description XML
|
|
237
|
+
*/
|
|
238
|
+
private async fetchDeviceDescription(device: ISsdpDevice): Promise<void> {
|
|
239
|
+
try {
|
|
240
|
+
const response = await fetch(device.location, {
|
|
241
|
+
signal: AbortSignal.timeout(5000),
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
if (!response.ok) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const xml = await response.text();
|
|
249
|
+
device.description = this.parseDeviceDescription(xml);
|
|
250
|
+
} catch {
|
|
251
|
+
// Ignore fetch errors
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Parse UPnP device description XML
|
|
257
|
+
*/
|
|
258
|
+
private parseDeviceDescription(xml: string): ISsdpDeviceDescription {
|
|
259
|
+
const getTagContent = (tag: string, source: string = xml): string => {
|
|
260
|
+
const regex = new RegExp(`<${tag}[^>]*>([^<]*)</${tag}>`, 'i');
|
|
261
|
+
const match = source.match(regex);
|
|
262
|
+
return match?.[1]?.trim() ?? '';
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const getTagBlock = (tag: string, source: string = xml): string => {
|
|
266
|
+
const regex = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)</${tag}>`, 'i');
|
|
267
|
+
const match = source.match(regex);
|
|
268
|
+
return match?.[1] ?? '';
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
// Parse services
|
|
272
|
+
const services: ISsdpService[] = [];
|
|
273
|
+
const serviceListBlock = getTagBlock('serviceList');
|
|
274
|
+
const serviceMatches = serviceListBlock.match(/<service>[\s\S]*?<\/service>/gi) || [];
|
|
275
|
+
|
|
276
|
+
for (const serviceXml of serviceMatches) {
|
|
277
|
+
services.push({
|
|
278
|
+
serviceType: getTagContent('serviceType', serviceXml),
|
|
279
|
+
serviceId: getTagContent('serviceId', serviceXml),
|
|
280
|
+
SCPDURL: getTagContent('SCPDURL', serviceXml),
|
|
281
|
+
controlURL: getTagContent('controlURL', serviceXml),
|
|
282
|
+
eventSubURL: getTagContent('eventSubURL', serviceXml),
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Parse icons
|
|
287
|
+
const icons: ISsdpIcon[] = [];
|
|
288
|
+
const iconListBlock = getTagBlock('iconList');
|
|
289
|
+
const iconMatches = iconListBlock.match(/<icon>[\s\S]*?<\/icon>/gi) || [];
|
|
290
|
+
|
|
291
|
+
for (const iconXml of iconMatches) {
|
|
292
|
+
icons.push({
|
|
293
|
+
mimetype: getTagContent('mimetype', iconXml),
|
|
294
|
+
width: parseInt(getTagContent('width', iconXml)) || 0,
|
|
295
|
+
height: parseInt(getTagContent('height', iconXml)) || 0,
|
|
296
|
+
depth: parseInt(getTagContent('depth', iconXml)) || 0,
|
|
297
|
+
url: getTagContent('url', iconXml),
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
deviceType: getTagContent('deviceType'),
|
|
303
|
+
friendlyName: getTagContent('friendlyName'),
|
|
304
|
+
manufacturer: getTagContent('manufacturer'),
|
|
305
|
+
manufacturerURL: getTagContent('manufacturerURL') || undefined,
|
|
306
|
+
modelDescription: getTagContent('modelDescription') || undefined,
|
|
307
|
+
modelName: getTagContent('modelName'),
|
|
308
|
+
modelNumber: getTagContent('modelNumber') || undefined,
|
|
309
|
+
modelURL: getTagContent('modelURL') || undefined,
|
|
310
|
+
serialNumber: getTagContent('serialNumber') || undefined,
|
|
311
|
+
UDN: getTagContent('UDN'),
|
|
312
|
+
services,
|
|
313
|
+
icons: icons.length > 0 ? icons : undefined,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Make a UPnP SOAP request
|
|
319
|
+
*/
|
|
320
|
+
public async soapRequest(
|
|
321
|
+
controlUrl: string,
|
|
322
|
+
serviceType: string,
|
|
323
|
+
action: string,
|
|
324
|
+
args: Record<string, string | number> = {}
|
|
325
|
+
): Promise<string> {
|
|
326
|
+
// Build SOAP body
|
|
327
|
+
let argsXml = '';
|
|
328
|
+
for (const [key, value] of Object.entries(args)) {
|
|
329
|
+
argsXml += `<${key}>${value}</${key}>`;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const soapBody = `<?xml version="1.0" encoding="utf-8"?>
|
|
333
|
+
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
|
|
334
|
+
<s:Body>
|
|
335
|
+
<u:${action} xmlns:u="${serviceType}">
|
|
336
|
+
${argsXml}
|
|
337
|
+
</u:${action}>
|
|
338
|
+
</s:Body>
|
|
339
|
+
</s:Envelope>`;
|
|
340
|
+
|
|
341
|
+
const response = await fetch(controlUrl, {
|
|
342
|
+
method: 'POST',
|
|
343
|
+
headers: {
|
|
344
|
+
'Content-Type': 'text/xml; charset=utf-8',
|
|
345
|
+
'SOAPACTION': `"${serviceType}#${action}"`,
|
|
346
|
+
},
|
|
347
|
+
body: soapBody,
|
|
348
|
+
signal: AbortSignal.timeout(10000),
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
if (!response.ok) {
|
|
352
|
+
throw new Error(`SOAP request failed: ${response.status}`);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return response.text();
|
|
356
|
+
}
|
|
357
|
+
}
|