@soundretouch/api 1.0.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/LICENSE +21 -0
- package/README.md +107 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/src/client/errors.d.ts +35 -0
- package/dist/src/client/errors.d.ts.map +1 -0
- package/dist/src/client/errors.js +44 -0
- package/dist/src/client/errors.js.map +1 -0
- package/dist/src/client/http.d.ts +40 -0
- package/dist/src/client/http.d.ts.map +1 -0
- package/dist/src/client/http.js +88 -0
- package/dist/src/client/http.js.map +1 -0
- package/dist/src/client/ws.d.ts +82 -0
- package/dist/src/client/ws.d.ts.map +1 -0
- package/dist/src/client/ws.js +153 -0
- package/dist/src/client/ws.js.map +1 -0
- package/dist/src/client/xml.d.ts +8 -0
- package/dist/src/client/xml.d.ts.map +1 -0
- package/dist/src/client/xml.js +18 -0
- package/dist/src/client/xml.js.map +1 -0
- package/dist/src/device/SoundTouchDevice.d.ts +457 -0
- package/dist/src/device/SoundTouchDevice.d.ts.map +1 -0
- package/dist/src/device/SoundTouchDevice.js +623 -0
- package/dist/src/device/SoundTouchDevice.js.map +1 -0
- package/dist/src/discovery/SoundTouchDiscovery.d.ts +18 -0
- package/dist/src/discovery/SoundTouchDiscovery.d.ts.map +1 -0
- package/dist/src/discovery/SoundTouchDiscovery.js +42 -0
- package/dist/src/discovery/SoundTouchDiscovery.js.map +1 -0
- package/dist/src/endpoints/audioProductLevelControls.d.ts +24 -0
- package/dist/src/endpoints/audioProductLevelControls.d.ts.map +1 -0
- package/dist/src/endpoints/audioProductLevelControls.js +44 -0
- package/dist/src/endpoints/audioProductLevelControls.js.map +1 -0
- package/dist/src/endpoints/audioProductToneControls.d.ts +24 -0
- package/dist/src/endpoints/audioProductToneControls.d.ts.map +1 -0
- package/dist/src/endpoints/audioProductToneControls.js +44 -0
- package/dist/src/endpoints/audioProductToneControls.js.map +1 -0
- package/dist/src/endpoints/audiodspcontrols.d.ts +24 -0
- package/dist/src/endpoints/audiodspcontrols.d.ts.map +1 -0
- package/dist/src/endpoints/audiodspcontrols.js +44 -0
- package/dist/src/endpoints/audiodspcontrols.js.map +1 -0
- package/dist/src/endpoints/bass.d.ts +23 -0
- package/dist/src/endpoints/bass.d.ts.map +1 -0
- package/dist/src/endpoints/bass.js +33 -0
- package/dist/src/endpoints/bass.js.map +1 -0
- package/dist/src/endpoints/bassCapabilities.d.ts +11 -0
- package/dist/src/endpoints/bassCapabilities.d.ts.map +1 -0
- package/dist/src/endpoints/bassCapabilities.js +16 -0
- package/dist/src/endpoints/bassCapabilities.js.map +1 -0
- package/dist/src/endpoints/capabilities.d.ts +11 -0
- package/dist/src/endpoints/capabilities.d.ts.map +1 -0
- package/dist/src/endpoints/capabilities.js +16 -0
- package/dist/src/endpoints/capabilities.js.map +1 -0
- package/dist/src/endpoints/info.d.ts +11 -0
- package/dist/src/endpoints/info.d.ts.map +1 -0
- package/dist/src/endpoints/info.js +16 -0
- package/dist/src/endpoints/info.js.map +1 -0
- package/dist/src/endpoints/key.d.ts +37 -0
- package/dist/src/endpoints/key.d.ts.map +1 -0
- package/dist/src/endpoints/key.js +45 -0
- package/dist/src/endpoints/key.js.map +1 -0
- package/dist/src/endpoints/name.d.ts +14 -0
- package/dist/src/endpoints/name.d.ts.map +1 -0
- package/dist/src/endpoints/name.js +23 -0
- package/dist/src/endpoints/name.js.map +1 -0
- package/dist/src/endpoints/nowPlaying.d.ts +11 -0
- package/dist/src/endpoints/nowPlaying.d.ts.map +1 -0
- package/dist/src/endpoints/nowPlaying.js +16 -0
- package/dist/src/endpoints/nowPlaying.js.map +1 -0
- package/dist/src/endpoints/presets.d.ts +11 -0
- package/dist/src/endpoints/presets.d.ts.map +1 -0
- package/dist/src/endpoints/presets.js +20 -0
- package/dist/src/endpoints/presets.js.map +1 -0
- package/dist/src/endpoints/select.d.ts +20 -0
- package/dist/src/endpoints/select.d.ts.map +1 -0
- package/dist/src/endpoints/select.js +33 -0
- package/dist/src/endpoints/select.js.map +1 -0
- package/dist/src/endpoints/sources.d.ts +11 -0
- package/dist/src/endpoints/sources.d.ts.map +1 -0
- package/dist/src/endpoints/sources.js +16 -0
- package/dist/src/endpoints/sources.js.map +1 -0
- package/dist/src/endpoints/trackInfo.d.ts +11 -0
- package/dist/src/endpoints/trackInfo.d.ts.map +1 -0
- package/dist/src/endpoints/trackInfo.js +16 -0
- package/dist/src/endpoints/trackInfo.js.map +1 -0
- package/dist/src/endpoints/volume.d.ts +21 -0
- package/dist/src/endpoints/volume.d.ts.map +1 -0
- package/dist/src/endpoints/volume.js +33 -0
- package/dist/src/endpoints/volume.js.map +1 -0
- package/dist/src/endpoints/zone.d.ts +57 -0
- package/dist/src/endpoints/zone.d.ts.map +1 -0
- package/dist/src/endpoints/zone.js +83 -0
- package/dist/src/endpoints/zone.js.map +1 -0
- package/dist/src/types/AudioDspControls.d.ts +7 -0
- package/dist/src/types/AudioDspControls.d.ts.map +1 -0
- package/dist/src/types/AudioDspControls.js +2 -0
- package/dist/src/types/AudioDspControls.js.map +1 -0
- package/dist/src/types/AudioProductLevelControls.d.ts +15 -0
- package/dist/src/types/AudioProductLevelControls.d.ts.map +1 -0
- package/dist/src/types/AudioProductLevelControls.js +2 -0
- package/dist/src/types/AudioProductLevelControls.js.map +1 -0
- package/dist/src/types/AudioProductToneControls.d.ts +15 -0
- package/dist/src/types/AudioProductToneControls.d.ts.map +1 -0
- package/dist/src/types/AudioProductToneControls.js +2 -0
- package/dist/src/types/AudioProductToneControls.js.map +1 -0
- package/dist/src/types/Bass.d.ts +6 -0
- package/dist/src/types/Bass.d.ts.map +1 -0
- package/dist/src/types/Bass.js +2 -0
- package/dist/src/types/Bass.js.map +1 -0
- package/dist/src/types/BassCapabilities.d.ts +8 -0
- package/dist/src/types/BassCapabilities.d.ts.map +1 -0
- package/dist/src/types/BassCapabilities.js +2 -0
- package/dist/src/types/BassCapabilities.js.map +1 -0
- package/dist/src/types/Capabilities.d.ts +10 -0
- package/dist/src/types/Capabilities.d.ts.map +1 -0
- package/dist/src/types/Capabilities.js +2 -0
- package/dist/src/types/Capabilities.js.map +1 -0
- package/dist/src/types/ContentItem.d.ts +5 -0
- package/dist/src/types/ContentItem.d.ts.map +1 -0
- package/dist/src/types/ContentItem.js +2 -0
- package/dist/src/types/ContentItem.js.map +1 -0
- package/dist/src/types/DeviceInfo.d.ts +22 -0
- package/dist/src/types/DeviceInfo.d.ts.map +1 -0
- package/dist/src/types/DeviceInfo.js +2 -0
- package/dist/src/types/DeviceInfo.js.map +1 -0
- package/dist/src/types/Enums.d.ts +8 -0
- package/dist/src/types/Enums.d.ts.map +1 -0
- package/dist/src/types/Enums.js +2 -0
- package/dist/src/types/Enums.js.map +1 -0
- package/dist/src/types/NowPlaying.d.ts +25 -0
- package/dist/src/types/NowPlaying.d.ts.map +1 -0
- package/dist/src/types/NowPlaying.js +2 -0
- package/dist/src/types/NowPlaying.js.map +1 -0
- package/dist/src/types/Presets.d.ts +15 -0
- package/dist/src/types/Presets.d.ts.map +1 -0
- package/dist/src/types/Presets.js +2 -0
- package/dist/src/types/Presets.js.map +1 -0
- package/dist/src/types/Recents.d.ts +14 -0
- package/dist/src/types/Recents.d.ts.map +1 -0
- package/dist/src/types/Recents.js +2 -0
- package/dist/src/types/Recents.js.map +1 -0
- package/dist/src/types/Sources.d.ts +12 -0
- package/dist/src/types/Sources.d.ts.map +1 -0
- package/dist/src/types/Sources.js +2 -0
- package/dist/src/types/Sources.js.map +1 -0
- package/dist/src/types/Updates.d.ts +37 -0
- package/dist/src/types/Updates.d.ts.map +1 -0
- package/dist/src/types/Updates.js +2 -0
- package/dist/src/types/Updates.js.map +1 -0
- package/dist/src/types/Volume.d.ts +6 -0
- package/dist/src/types/Volume.d.ts.map +1 -0
- package/dist/src/types/Volume.js +2 -0
- package/dist/src/types/Volume.js.map +1 -0
- package/dist/src/types/Zone.d.ts +22 -0
- package/dist/src/types/Zone.d.ts.map +1 -0
- package/dist/src/types/Zone.js +2 -0
- package/dist/src/types/Zone.js.map +1 -0
- package/docs/SoundTouch-Web-API.pdf +0 -0
- package/package.json +86 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 RemcoS2000
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# SoundRetouch-API
|
|
2
|
+
|
|
3
|
+
This is another unofficial TypeScript API library to configure and manage legacy Bose SoundTouch devices.
|
|
4
|
+
|
|
5
|
+
This project is not affiliated with Bose.
|
|
6
|
+
|
|
7
|
+
As Bose announced the deprecation of the SoundTouch lineup and will stop cloud support, I wanted to try something out and set up a project to help keep these speakers usable. This project provides an easy-to-use wrapper on the SoundTouch API documented by Bose, and it supports device discovery as well.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm i soundretouch-api
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { SoundTouchDevice } from 'soundretouch-api'
|
|
19
|
+
|
|
20
|
+
const device = new SoundTouchDevice('192.168.1.67')
|
|
21
|
+
|
|
22
|
+
const info = await device.info()
|
|
23
|
+
const nowPlaying = await device.nowPlaying()
|
|
24
|
+
const volume = await device.volume()
|
|
25
|
+
|
|
26
|
+
await device.setVolume(25)
|
|
27
|
+
await device.keyPressAndRelease('PLAY')
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## WebSocket Asynchronous Notifications
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { SoundTouchDevice } from 'soundretouch-api'
|
|
34
|
+
|
|
35
|
+
const device = new SoundTouchDevice('192.168.1.67')
|
|
36
|
+
|
|
37
|
+
device.onNowPlayingUpdated((nowPlaying) => {
|
|
38
|
+
console.log('Now playing changed:', nowPlaying)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
device.onVolumeUpdated((volume) => {
|
|
42
|
+
console.log('Volume changed:', volume)
|
|
43
|
+
})
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Discovery
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { SoundTouchDiscovery } from 'soundretouch-api'
|
|
50
|
+
|
|
51
|
+
const handle = SoundTouchDiscovery.start(async (device) => {
|
|
52
|
+
console.log('Found device at', device.host)
|
|
53
|
+
console.log(await device.info())
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
// Stop after 2 minutes
|
|
57
|
+
setTimeout(() => handle.stop(), 120000)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Implemented Endpoints
|
|
61
|
+
|
|
62
|
+
- /info
|
|
63
|
+
- /now_playing
|
|
64
|
+
- /trackInfo
|
|
65
|
+
- /sources
|
|
66
|
+
- /select
|
|
67
|
+
- /volume
|
|
68
|
+
- /bass
|
|
69
|
+
- /bassCapabilities
|
|
70
|
+
- /capabilities
|
|
71
|
+
- /audiodspcontrols
|
|
72
|
+
- /audioproducttonecontrols
|
|
73
|
+
- /audioproductlevelcontrols
|
|
74
|
+
- /presets
|
|
75
|
+
- /name
|
|
76
|
+
- /getZone
|
|
77
|
+
- /setZone
|
|
78
|
+
- /addZoneSlave
|
|
79
|
+
- /removeZoneSlave
|
|
80
|
+
- /key
|
|
81
|
+
|
|
82
|
+
## Debug Logging
|
|
83
|
+
|
|
84
|
+
Enable debug output with the `DEBUG` environment variable. You can target all logs or narrow it down to a single subsystem.
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
DEBUG=soundretouch:*
|
|
88
|
+
DEBUG=soundretouch:discovery
|
|
89
|
+
DEBUG=soundretouch:endpoints:*
|
|
90
|
+
DEBUG=soundretouch:endpoints:info
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Smoke Test
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npx tsx scripts/smoke.ts
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Docs
|
|
100
|
+
|
|
101
|
+
- [`docs/SoundTouch-Web-API.pdf`](docs/SoundTouch-Web-API.pdf)
|
|
102
|
+
|
|
103
|
+
## Compliance Notice
|
|
104
|
+
|
|
105
|
+
This project uses the Bose SoundTouch Web API. Use of the SoundTouch Materials is subject to Bose's Terms of Use. Refer to the official documentation for the full terms.
|
|
106
|
+
|
|
107
|
+
Trademark attribution: Bose and SoundTouch are trademarks of Bose Corporation.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export { SoundTouchDevice } from './src/device/SoundTouchDevice';
|
|
2
|
+
export type { DeviceInfo, DeviceInfoComponent, DeviceNetworkInfo } from './src/types/DeviceInfo';
|
|
3
|
+
export type { Bass } from './src/types/Bass';
|
|
4
|
+
export type { BassCapabilities } from './src/types/BassCapabilities';
|
|
5
|
+
export type { Capabilities, Capability } from './src/types/Capabilities';
|
|
6
|
+
export type { AudioDspControls } from './src/types/AudioDspControls';
|
|
7
|
+
export type { ArtStatus, AudioMode, KeyState, KeyValue, PlayStatus, PresetId, SourceStatus } from './src/types/Enums';
|
|
8
|
+
export type { AudioProductToneControls, AudioProductToneControlsUpdate, ToneControl } from './src/types/AudioProductToneControls';
|
|
9
|
+
export type { AudioProductLevelControls, AudioProductLevelControlsUpdate, LevelControl } from './src/types/AudioProductLevelControls';
|
|
10
|
+
export type { NowPlaying } from './src/types/NowPlaying';
|
|
11
|
+
export type { Presets, Preset, PresetContentItem } from './src/types/Presets';
|
|
12
|
+
export type { Recents, Recent, RecentContentItem } from './src/types/Recents';
|
|
13
|
+
export type { Zone, ZoneMember, ZoneConfig, ZoneConfigMember, ZoneSlaveConfig } from './src/types/Zone';
|
|
14
|
+
export type { ContentItem } from './src/types/ContentItem';
|
|
15
|
+
export type { Sources, SourceItem } from './src/types/Sources';
|
|
16
|
+
export type { Volume } from './src/types/Volume';
|
|
17
|
+
export type { Updates } from './src/types/Updates';
|
|
18
|
+
export type { SoundTouchKey } from './src/endpoints/key';
|
|
19
|
+
export type { HttpClientOptions } from './src/client/http';
|
|
20
|
+
export type { WebSocketClientOptions } from './src/client/ws';
|
|
21
|
+
export type { SoundTouchDeviceOptions } from './src/device/SoundTouchDevice';
|
|
22
|
+
export { SoundTouchError, HttpError, TimeoutError } from './src/client/errors';
|
|
23
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAA;AAChE,YAAY,EAAE,UAAU,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAChG,YAAY,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAA;AAC5C,YAAY,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AACpE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AACxE,YAAY,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AACpE,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACrH,YAAY,EAAE,wBAAwB,EAAE,8BAA8B,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAA;AACjI,YAAY,EAAE,yBAAyB,EAAE,+BAA+B,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAA;AACrI,YAAY,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACxD,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAC7E,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAC7E,YAAY,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AACvG,YAAY,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAC1D,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAC9D,YAAY,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAChD,YAAY,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACxD,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAC1D,YAAY,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AAC7D,YAAY,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAA;AAC5E,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAA;AAqBhE,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base error type for SoundTouch client operations.
|
|
3
|
+
*/
|
|
4
|
+
export declare class SoundTouchError extends Error {
|
|
5
|
+
constructor(message: string);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Error thrown when an HTTP request fails with a non-2xx status code.
|
|
9
|
+
*/
|
|
10
|
+
export declare class HttpError extends SoundTouchError {
|
|
11
|
+
status: number;
|
|
12
|
+
statusText: string;
|
|
13
|
+
body?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Creates a new HttpError instance.
|
|
16
|
+
*
|
|
17
|
+
* @param status HTTP status code.
|
|
18
|
+
* @param statusText HTTP status text.
|
|
19
|
+
* @param body Optional response body.
|
|
20
|
+
*/
|
|
21
|
+
constructor(status: number, statusText: string, body?: string);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Error thrown when a request exceeds the configured timeout.
|
|
25
|
+
*/
|
|
26
|
+
export declare class TimeoutError extends SoundTouchError {
|
|
27
|
+
timeoutMs: number;
|
|
28
|
+
/**
|
|
29
|
+
* Creates a new TimeoutError instance.
|
|
30
|
+
*
|
|
31
|
+
* @param timeoutMs Timeout value in milliseconds.
|
|
32
|
+
*/
|
|
33
|
+
constructor(timeoutMs: number);
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/client/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,eAAgB,SAAQ,KAAK;gBAC1B,OAAO,EAAE,MAAM;CAI9B;AAED;;GAEG;AACH,qBAAa,SAAU,SAAQ,eAAe;IAC1C,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,CAAA;IAEb;;;;;;OAMG;gBACS,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;CAOhE;AAED;;GAEG;AACH,qBAAa,YAAa,SAAQ,eAAe;IAC7C,SAAS,EAAE,MAAM,CAAA;IAEjB;;;;OAIG;gBACS,SAAS,EAAE,MAAM;CAKhC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base error type for SoundTouch client operations.
|
|
3
|
+
*/
|
|
4
|
+
export class SoundTouchError extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = 'SoundTouchError';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Error thrown when an HTTP request fails with a non-2xx status code.
|
|
12
|
+
*/
|
|
13
|
+
export class HttpError extends SoundTouchError {
|
|
14
|
+
/**
|
|
15
|
+
* Creates a new HttpError instance.
|
|
16
|
+
*
|
|
17
|
+
* @param status HTTP status code.
|
|
18
|
+
* @param statusText HTTP status text.
|
|
19
|
+
* @param body Optional response body.
|
|
20
|
+
*/
|
|
21
|
+
constructor(status, statusText, body) {
|
|
22
|
+
super(`HTTP ${status} ${statusText}`);
|
|
23
|
+
this.name = 'HttpError';
|
|
24
|
+
this.status = status;
|
|
25
|
+
this.statusText = statusText;
|
|
26
|
+
this.body = body;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Error thrown when a request exceeds the configured timeout.
|
|
31
|
+
*/
|
|
32
|
+
export class TimeoutError extends SoundTouchError {
|
|
33
|
+
/**
|
|
34
|
+
* Creates a new TimeoutError instance.
|
|
35
|
+
*
|
|
36
|
+
* @param timeoutMs Timeout value in milliseconds.
|
|
37
|
+
*/
|
|
38
|
+
constructor(timeoutMs) {
|
|
39
|
+
super(`Request timed out after ${timeoutMs}ms`);
|
|
40
|
+
this.name = 'TimeoutError';
|
|
41
|
+
this.timeoutMs = timeoutMs;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/client/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,OAAO,eAAgB,SAAQ,KAAK;IACtC,YAAY,OAAe;QACvB,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAA;IACjC,CAAC;CACJ;AAED;;GAEG;AACH,MAAM,OAAO,SAAU,SAAQ,eAAe;IAK1C;;;;;;OAMG;IACH,YAAY,MAAc,EAAE,UAAkB,EAAE,IAAa;QACzD,KAAK,CAAC,QAAQ,MAAM,IAAI,UAAU,EAAE,CAAC,CAAA;QACrC,IAAI,CAAC,IAAI,GAAG,WAAW,CAAA;QACvB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;QACpB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;QAC5B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;IACpB,CAAC;CACJ;AAED;;GAEG;AACH,MAAM,OAAO,YAAa,SAAQ,eAAe;IAG7C;;;;OAIG;IACH,YAAY,SAAiB;QACzB,KAAK,CAAC,2BAA2B,SAAS,IAAI,CAAC,CAAA;QAC/C,IAAI,CAAC,IAAI,GAAG,cAAc,CAAA;QAC1B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;IAC9B,CAAC;CACJ"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface HttpClientOptions {
|
|
2
|
+
timeoutMs?: number;
|
|
3
|
+
}
|
|
4
|
+
export declare class HttpClient {
|
|
5
|
+
private host;
|
|
6
|
+
private timeoutMs;
|
|
7
|
+
private log;
|
|
8
|
+
/**
|
|
9
|
+
* Creates a new HTTP client for a SoundTouch device.
|
|
10
|
+
*
|
|
11
|
+
* @param host The hostname or IP address of the SoundTouch device.
|
|
12
|
+
* @param options Optional HTTP client configuration.
|
|
13
|
+
*/
|
|
14
|
+
constructor(host: string, options?: HttpClientOptions);
|
|
15
|
+
/**
|
|
16
|
+
* Sends a GET request and parses the XML response into the expected type.
|
|
17
|
+
*
|
|
18
|
+
* @param path Endpoint path to request.
|
|
19
|
+
* @returns Promise<T> A promise that resolves to the parsed XML response.
|
|
20
|
+
*/
|
|
21
|
+
getXml<T>(path: string): Promise<T>;
|
|
22
|
+
/**
|
|
23
|
+
* Sends a POST request and parses the XML response into the expected type.
|
|
24
|
+
*
|
|
25
|
+
* @param path Endpoint path to request.
|
|
26
|
+
* @param body Optional XML body to send.
|
|
27
|
+
* @returns Promise<T> A promise that resolves to the parsed XML response.
|
|
28
|
+
*/
|
|
29
|
+
postXml<T>(path: string, body?: string): Promise<T>;
|
|
30
|
+
/**
|
|
31
|
+
* Sends a POST request without expecting a response body.
|
|
32
|
+
*
|
|
33
|
+
* @param path Endpoint path to request.
|
|
34
|
+
* @param body Optional XML body to send.
|
|
35
|
+
* @returns Promise<void> A promise that resolves when the request completes.
|
|
36
|
+
*/
|
|
37
|
+
post(path: string, body?: string): Promise<void>;
|
|
38
|
+
private request;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../../src/client/http.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,iBAAiB;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,qBAAa,UAAU;IACnB,OAAO,CAAC,IAAI,CAAQ;IACpB,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,GAAG,CAAmC;IAE9C;;;;;OAKG;gBACS,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,iBAAsB;IAKzD;;;;;OAKG;IACG,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAKzC;;;;;;OAMG;IACG,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAczD;;;;;;OAMG;IACG,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAQxC,OAAO;CA+BxB"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import createDebug from 'debug';
|
|
2
|
+
import { HttpError, TimeoutError } from './errors';
|
|
3
|
+
import { parseXml } from './xml';
|
|
4
|
+
export class HttpClient {
|
|
5
|
+
/**
|
|
6
|
+
* Creates a new HTTP client for a SoundTouch device.
|
|
7
|
+
*
|
|
8
|
+
* @param host The hostname or IP address of the SoundTouch device.
|
|
9
|
+
* @param options Optional HTTP client configuration.
|
|
10
|
+
*/
|
|
11
|
+
constructor(host, options = {}) {
|
|
12
|
+
this.log = createDebug('soundretouch:http');
|
|
13
|
+
this.host = host;
|
|
14
|
+
this.timeoutMs = options.timeoutMs ?? 10000;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Sends a GET request and parses the XML response into the expected type.
|
|
18
|
+
*
|
|
19
|
+
* @param path Endpoint path to request.
|
|
20
|
+
* @returns Promise<T> A promise that resolves to the parsed XML response.
|
|
21
|
+
*/
|
|
22
|
+
async getXml(path) {
|
|
23
|
+
const text = await this.request(path, { method: 'GET' });
|
|
24
|
+
return parseXml(text);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Sends a POST request and parses the XML response into the expected type.
|
|
28
|
+
*
|
|
29
|
+
* @param path Endpoint path to request.
|
|
30
|
+
* @param body Optional XML body to send.
|
|
31
|
+
* @returns Promise<T> A promise that resolves to the parsed XML response.
|
|
32
|
+
*/
|
|
33
|
+
async postXml(path, body) {
|
|
34
|
+
const text = await this.request(path, {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
body,
|
|
37
|
+
headers: { 'Content-Type': 'text/xml' },
|
|
38
|
+
});
|
|
39
|
+
if (!text) {
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
return parseXml(text);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Sends a POST request without expecting a response body.
|
|
46
|
+
*
|
|
47
|
+
* @param path Endpoint path to request.
|
|
48
|
+
* @param body Optional XML body to send.
|
|
49
|
+
* @returns Promise<void> A promise that resolves when the request completes.
|
|
50
|
+
*/
|
|
51
|
+
async post(path, body) {
|
|
52
|
+
await this.request(path, {
|
|
53
|
+
method: 'POST',
|
|
54
|
+
body,
|
|
55
|
+
headers: { 'Content-Type': 'text/xml' },
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
async request(path, init) {
|
|
59
|
+
const url = `http://${this.host}:8090${path.startsWith('/') ? path : `/${path}`}`;
|
|
60
|
+
this.log('request %s %s', init.method, url);
|
|
61
|
+
const controller = new AbortController();
|
|
62
|
+
const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
63
|
+
try {
|
|
64
|
+
const response = await fetch(url, {
|
|
65
|
+
...init,
|
|
66
|
+
signal: controller.signal,
|
|
67
|
+
});
|
|
68
|
+
const text = await response.text();
|
|
69
|
+
this.log('response %s %s %s', init.method, url, response.status);
|
|
70
|
+
if (!response.ok) {
|
|
71
|
+
throw new HttpError(response.status, response.statusText, text);
|
|
72
|
+
}
|
|
73
|
+
return text;
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
if (error && typeof error === 'object' && 'name' in error && error.name === 'AbortError') {
|
|
77
|
+
this.log('timeout %s %s', init.method, url);
|
|
78
|
+
throw new TimeoutError(this.timeoutMs);
|
|
79
|
+
}
|
|
80
|
+
this.log('error %s %s %O', init.method, url, error);
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
clearTimeout(timeout);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../../src/client/http.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export type WebSocketClientOptions = {
|
|
2
|
+
autoReconnect?: boolean;
|
|
3
|
+
reconnectDelayMs?: number;
|
|
4
|
+
unwrap?: boolean;
|
|
5
|
+
};
|
|
6
|
+
type MessageHandler<T> = (payload: T) => void;
|
|
7
|
+
type ErrorHandler = (error: unknown) => void;
|
|
8
|
+
export declare class WebSocketClient {
|
|
9
|
+
private host;
|
|
10
|
+
private socket?;
|
|
11
|
+
private options;
|
|
12
|
+
private reconnectTimer?;
|
|
13
|
+
private closedByUser;
|
|
14
|
+
private messageHandlers;
|
|
15
|
+
private errorHandlers;
|
|
16
|
+
private log;
|
|
17
|
+
constructor(host: string, options?: WebSocketClientOptions);
|
|
18
|
+
/**
|
|
19
|
+
* Opens a WebSocket connection for async updates.
|
|
20
|
+
*
|
|
21
|
+
* WebSocket connections are made on port 8080 using the "gabbo" protocol.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* const client = new WebSocketClient('192.168.1.5')
|
|
25
|
+
* client.connect()
|
|
26
|
+
*/
|
|
27
|
+
connect(): void;
|
|
28
|
+
/**
|
|
29
|
+
* Closes the WebSocket connection, if one is open.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* const client = new WebSocketClient('192.168.1.5')
|
|
33
|
+
* client.close()
|
|
34
|
+
*/
|
|
35
|
+
close(): void;
|
|
36
|
+
/**
|
|
37
|
+
* Subscribes to parsed message payloads.
|
|
38
|
+
*
|
|
39
|
+
* @param handler Callback invoked with each parsed payload.
|
|
40
|
+
* @returns Unsubscribe function.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* const off = client.onMessage((payload) => console.log(payload))
|
|
44
|
+
* off()
|
|
45
|
+
*/
|
|
46
|
+
onMessage<T>(handler: MessageHandler<T>): () => void;
|
|
47
|
+
/**
|
|
48
|
+
* Subscribes to WebSocket errors.
|
|
49
|
+
*
|
|
50
|
+
* @param handler Callback invoked when a WebSocket error is raised.
|
|
51
|
+
* @returns Unsubscribe function.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* const off = client.onError((error) => console.error(error))
|
|
55
|
+
* off()
|
|
56
|
+
*/
|
|
57
|
+
onError(handler: ErrorHandler): () => void;
|
|
58
|
+
/**
|
|
59
|
+
* Indicates whether a WebSocket connection is currently active.
|
|
60
|
+
*
|
|
61
|
+
* @returns True when the socket is connected.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* if (client.isConnected()) {
|
|
65
|
+
* console.log('connected')
|
|
66
|
+
* }
|
|
67
|
+
*/
|
|
68
|
+
isConnected(): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Ensures the WebSocket connection is established.
|
|
71
|
+
*
|
|
72
|
+
* @returns True when the socket is connected.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* client.ensureConnected()
|
|
76
|
+
*/
|
|
77
|
+
ensureConnected(): boolean;
|
|
78
|
+
private unwrapPayload;
|
|
79
|
+
private clearReconnect;
|
|
80
|
+
}
|
|
81
|
+
export {};
|
|
82
|
+
//# sourceMappingURL=ws.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws.d.ts","sourceRoot":"","sources":["../../../src/client/ws.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,sBAAsB,GAAG;IACjC,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,MAAM,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,KAAK,cAAc,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,IAAI,CAAA;AAC7C,KAAK,YAAY,GAAG,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;AAE5C,qBAAa,eAAe;IACxB,OAAO,CAAC,IAAI,CAAQ;IACpB,OAAO,CAAC,MAAM,CAAC,CAAW;IAC1B,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,cAAc,CAAC,CAA+B;IACtD,OAAO,CAAC,YAAY,CAAQ;IAC5B,OAAO,CAAC,eAAe,CAAwC;IAC/D,OAAO,CAAC,aAAa,CAA0B;IAE/C,OAAO,CAAC,GAAG,CAAwC;gBAEvC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,sBAA2B;IAK9D;;;;;;;;OAQG;IACH,OAAO,IAAI,IAAI;IA4Cf;;;;;;OAMG;IACH,KAAK,IAAI,IAAI;IAcb;;;;;;;;;OASG;IACH,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;IAMpD;;;;;;;;;OASG;IACH,OAAO,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,IAAI;IAK1C;;;;;;;;;OASG;IACH,WAAW,IAAI,OAAO;IAItB;;;;;;;OAOG;IACH,eAAe,IAAI,OAAO;IAQ1B,OAAO,CAAC,aAAa;IAcrB,OAAO,CAAC,cAAc;CASzB"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import createDebug from 'debug';
|
|
2
|
+
import { parseXml } from './xml';
|
|
3
|
+
export class WebSocketClient {
|
|
4
|
+
constructor(host, options = {}) {
|
|
5
|
+
this.closedByUser = false;
|
|
6
|
+
this.messageHandlers = new Set();
|
|
7
|
+
this.errorHandlers = new Set();
|
|
8
|
+
this.log = createDebug('soundretouch:client:ws');
|
|
9
|
+
this.host = host;
|
|
10
|
+
this.options = options;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Opens a WebSocket connection for async updates.
|
|
14
|
+
*
|
|
15
|
+
* WebSocket connections are made on port 8080 using the "gabbo" protocol.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* const client = new WebSocketClient('192.168.1.5')
|
|
19
|
+
* client.connect()
|
|
20
|
+
*/
|
|
21
|
+
connect() {
|
|
22
|
+
if (this.socket) {
|
|
23
|
+
this.log('connect skipped (already connected)');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
this.closedByUser = false;
|
|
27
|
+
this.clearReconnect();
|
|
28
|
+
const url = `ws://${this.host}:8080`;
|
|
29
|
+
this.log('connect %s', url);
|
|
30
|
+
this.socket = new WebSocket(url, 'gabbo');
|
|
31
|
+
this.socket.addEventListener('message', (event) => {
|
|
32
|
+
try {
|
|
33
|
+
const data = typeof event.data === 'string' ? event.data : new TextDecoder().decode(event.data);
|
|
34
|
+
this.log('message received', data);
|
|
35
|
+
const payload = parseXml(data);
|
|
36
|
+
const message = this.unwrapPayload(payload);
|
|
37
|
+
this.log('message parsed %O', message);
|
|
38
|
+
this.messageHandlers.forEach((handler) => handler(message));
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
this.log('message error %O', error);
|
|
42
|
+
this.errorHandlers.forEach((handler) => handler(error));
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
this.socket.addEventListener('error', (event) => {
|
|
46
|
+
this.log('socket error %O', event);
|
|
47
|
+
this.errorHandlers.forEach((handler) => handler(event));
|
|
48
|
+
});
|
|
49
|
+
this.socket.addEventListener('close', () => {
|
|
50
|
+
this.log('socket close');
|
|
51
|
+
this.socket = undefined;
|
|
52
|
+
if (!this.closedByUser && this.options.autoReconnect) {
|
|
53
|
+
const delay = this.options.reconnectDelayMs ?? 1000;
|
|
54
|
+
this.log('reconnect scheduled in %dms', delay);
|
|
55
|
+
this.reconnectTimer = setTimeout(() => this.connect(), delay);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Closes the WebSocket connection, if one is open.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* const client = new WebSocketClient('192.168.1.5')
|
|
64
|
+
* client.close()
|
|
65
|
+
*/
|
|
66
|
+
close() {
|
|
67
|
+
this.closedByUser = true;
|
|
68
|
+
this.clearReconnect();
|
|
69
|
+
if (!this.socket) {
|
|
70
|
+
this.log('close skipped (no active connection)');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
this.log('close');
|
|
74
|
+
this.socket.close();
|
|
75
|
+
this.socket = undefined;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Subscribes to parsed message payloads.
|
|
79
|
+
*
|
|
80
|
+
* @param handler Callback invoked with each parsed payload.
|
|
81
|
+
* @returns Unsubscribe function.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* const off = client.onMessage((payload) => console.log(payload))
|
|
85
|
+
* off()
|
|
86
|
+
*/
|
|
87
|
+
onMessage(handler) {
|
|
88
|
+
const wrapped = handler;
|
|
89
|
+
this.messageHandlers.add(wrapped);
|
|
90
|
+
return () => this.messageHandlers.delete(wrapped);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Subscribes to WebSocket errors.
|
|
94
|
+
*
|
|
95
|
+
* @param handler Callback invoked when a WebSocket error is raised.
|
|
96
|
+
* @returns Unsubscribe function.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* const off = client.onError((error) => console.error(error))
|
|
100
|
+
* off()
|
|
101
|
+
*/
|
|
102
|
+
onError(handler) {
|
|
103
|
+
this.errorHandlers.add(handler);
|
|
104
|
+
return () => this.errorHandlers.delete(handler);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Indicates whether a WebSocket connection is currently active.
|
|
108
|
+
*
|
|
109
|
+
* @returns True when the socket is connected.
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* if (client.isConnected()) {
|
|
113
|
+
* console.log('connected')
|
|
114
|
+
* }
|
|
115
|
+
*/
|
|
116
|
+
isConnected() {
|
|
117
|
+
return this.socket !== undefined;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Ensures the WebSocket connection is established.
|
|
121
|
+
*
|
|
122
|
+
* @returns True when the socket is connected.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* client.ensureConnected()
|
|
126
|
+
*/
|
|
127
|
+
ensureConnected() {
|
|
128
|
+
if (!this.isConnected()) {
|
|
129
|
+
this.connect();
|
|
130
|
+
}
|
|
131
|
+
return this.isConnected();
|
|
132
|
+
}
|
|
133
|
+
unwrapPayload(payload) {
|
|
134
|
+
if (!this.options.unwrap || !payload || typeof payload !== 'object') {
|
|
135
|
+
return payload;
|
|
136
|
+
}
|
|
137
|
+
const record = payload;
|
|
138
|
+
const keys = Object.keys(record);
|
|
139
|
+
if (keys.length !== 1) {
|
|
140
|
+
return payload;
|
|
141
|
+
}
|
|
142
|
+
return record[keys[0]] ?? payload;
|
|
143
|
+
}
|
|
144
|
+
clearReconnect() {
|
|
145
|
+
if (!this.reconnectTimer) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
clearTimeout(this.reconnectTimer);
|
|
149
|
+
this.reconnectTimer = undefined;
|
|
150
|
+
this.log('reconnect cleared');
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=ws.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws.js","sourceRoot":"","sources":["../../../src/client/ws.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses XML into the requested type using the configured XML parser.
|
|
3
|
+
*
|
|
4
|
+
* @param xml Raw XML string to parse.
|
|
5
|
+
* @returns Parsed XML payload typed as the requested generic type.
|
|
6
|
+
*/
|
|
7
|
+
export declare function parseXml<T>(xml: string): T;
|
|
8
|
+
//# sourceMappingURL=xml.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xml.d.ts","sourceRoot":"","sources":["../../../src/client/xml.ts"],"names":[],"mappings":"AAUA;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,CAE1C"}
|