abb-rws-client 0.7.1 → 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/CHANGELOG.md +169 -0
- package/README.md +61 -24
- package/dist/HalJsonParser.d.ts +55 -0
- package/dist/HalJsonParser.d.ts.map +1 -0
- package/dist/HalJsonParser.js +155 -0
- package/dist/HalJsonParser.js.map +1 -0
- package/dist/HttpSession.d.ts.map +1 -1
- package/dist/HttpSession.js +13 -2
- package/dist/HttpSession.js.map +1 -1
- package/dist/IRWSAdapter.d.ts +7 -1
- package/dist/IRWSAdapter.d.ts.map +1 -1
- package/dist/MdnsDiscovery.d.ts +57 -0
- package/dist/MdnsDiscovery.d.ts.map +1 -0
- package/dist/MdnsDiscovery.js +313 -0
- package/dist/MdnsDiscovery.js.map +1 -0
- package/dist/MultiRobotManager.d.ts +5 -2
- package/dist/MultiRobotManager.d.ts.map +1 -1
- package/dist/MultiRobotManager.js +8 -3
- package/dist/MultiRobotManager.js.map +1 -1
- package/dist/RWS1Adapter.d.ts +60 -2
- package/dist/RWS1Adapter.d.ts.map +1 -1
- package/dist/RWS1Adapter.js +152 -4
- package/dist/RWS1Adapter.js.map +1 -1
- package/dist/ResourceMapper.d.ts +4 -0
- package/dist/ResourceMapper.d.ts.map +1 -1
- package/dist/ResourceMapper.js +9 -1
- package/dist/ResourceMapper.js.map +1 -1
- package/dist/RobotManager.d.ts +72 -12
- package/dist/RobotManager.d.ts.map +1 -1
- package/dist/RobotManager.js +255 -50
- package/dist/RobotManager.js.map +1 -1
- package/dist/RwsClient2.d.ts +150 -10
- package/dist/RwsClient2.d.ts.map +1 -1
- package/dist/RwsClient2.js +599 -236
- package/dist/RwsClient2.js.map +1 -1
- package/dist/WsSubscriber.d.ts +31 -5
- package/dist/WsSubscriber.d.ts.map +1 -1
- package/dist/WsSubscriber.js +104 -50
- package/dist/WsSubscriber.js.map +1 -1
- package/dist/detect.d.ts +12 -3
- package/dist/detect.d.ts.map +1 -1
- package/dist/detect.js +69 -25
- package/dist/detect.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/types.js +3 -3
- package/dist/types.js.map +1 -1
- package/examples/05-remote-control-rmmp.mjs +10 -12
- package/examples/06-pull-module-source.mjs +13 -20
- package/package.json +62 -60
- package/src/HalJsonParser.ts +137 -0
- package/src/HttpSession.ts +460 -0
- package/src/IRWSAdapter.ts +422 -0
- package/src/Logger.ts +54 -0
- package/src/MdnsDiscovery.ts +336 -0
- package/src/MultiRobotManager.ts +159 -0
- package/src/RWS1Adapter.ts +1018 -0
- package/src/RWS2Adapter.ts +19 -0
- package/src/ResourceMapper.ts +517 -0
- package/src/ResponseParser.ts +710 -0
- package/src/RobotManager.ts +1705 -0
- package/src/RwsClient.ts +1150 -0
- package/src/RwsClient2.ts +2214 -0
- package/src/WsSubscriber.ts +350 -0
- package/src/XhtmlParser.ts +53 -0
- package/src/detect.ts +261 -0
- package/src/index.ts +83 -0
- package/src/types.ts +336 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,175 @@ All notable changes to `abb-rws-client` are documented here.
|
|
|
4
4
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [1.0.0] — 2026-07-09
|
|
8
|
+
|
|
9
|
+
The library is feature-complete for both controller generations and every wire
|
|
10
|
+
recipe below has been verified against live controllers (RW 6.16 IRC5 +
|
|
11
|
+
RW 7.21 OmniCore). Time to call it 1.0.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **mDNS/Bonjour controller discovery** — `RobotManager.discoverControllersMdns()`
|
|
16
|
+
finds every ABB controller (real or virtual) announcing on the local network:
|
|
17
|
+
zero-dependency DNS-SD implementation, returns system name, host, RWS port,
|
|
18
|
+
RobotWare version, system GUID, and a protocol classification. No more port
|
|
19
|
+
scanning to find RobotStudio's randomly-assigned VC ports.
|
|
20
|
+
- **HAL JSON parsing for RWS 2.0** — GETs negotiate
|
|
21
|
+
`application/hal+json;v=2.0` (officially supported; live-verified) with an
|
|
22
|
+
automatic, remembered per-controller fallback to XHTML for older RobotWare 7
|
|
23
|
+
releases. Byte-identical results across both representations verified live
|
|
24
|
+
over seven read families.
|
|
25
|
+
- **Simulation panel (virtual controllers, RW7)** — `simEmergencyStop()`,
|
|
26
|
+
`simResetEmergencyStop()`, `simGeneralStop()`, `simAutoStop()`,
|
|
27
|
+
`simEnableSwitch(on)`, and `teleportMechunit(mechunit, joints)`. Drive the
|
|
28
|
+
E-stop/guard-stop chain and reposition the simulated robot from the API —
|
|
29
|
+
every recipe converged from live controller validation errors, including the
|
|
30
|
+
undocumented inverted `state=off`-engages polarity.
|
|
31
|
+
- **`RWS1Adapter.saveModule` migrated off a dead endpoint** — the legacy
|
|
32
|
+
`?action=savemod` form returns a blanket 400 on RW6 regardless of body; the
|
|
33
|
+
method now uses the live-verified `?action=save` recipe (and percent-encodes
|
|
34
|
+
the destination path).
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- Published packages now include `src/` so the shipped source maps resolve;
|
|
39
|
+
the `exports` map lists `types` first for `nodenext` consumers.
|
|
40
|
+
- 169 new unit tests (368 total): every previously-untested `ResourceMapper`
|
|
41
|
+
builder and `ResponseParser` parser, the RWS 1.0 digest/cookie/pacing
|
|
42
|
+
request path, the mDNS wire parser, HAL/XHTML parity, and the simulation
|
|
43
|
+
panel.
|
|
44
|
+
|
|
45
|
+
## [0.8.0] — 2026-07-09
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- **RWS 2.0 real-time subscriptions actually work now.** The WebSocket handshake
|
|
50
|
+
offered `robapi2_subscription` — the RWS **1.0** subprotocol name — and
|
|
51
|
+
RobotWare 7 rejects it with HTTP 400, so every OmniCore connection silently
|
|
52
|
+
fell back to polling since the feature shipped. The client now offers the
|
|
53
|
+
official `rws_subscription` (RWS 2.0 manual 3HAC073675-001). Live-verified on
|
|
54
|
+
RW 7.21: handshake 101, real event frames delivered.
|
|
55
|
+
- **RWS 2.0 WebSocket drops no longer kill live updates for the session** — the
|
|
56
|
+
25 s ping interval is cleared on close, the client re-registers the
|
|
57
|
+
subscription with bounded exponential backoff, and when it finally gives up
|
|
58
|
+
it tells the owner (see `onLost` below) so `RobotManager` can restore fast
|
|
59
|
+
polling instead of idling at the slow cadence forever.
|
|
60
|
+
- **`getModuleSource` works for modules with no backing file** (loaded from
|
|
61
|
+
`.pgf`, RobotStudio, or the FlexPendant — [abb-rws-vscode#3](https://github.com/ichbinmeraj/abb-rws-vscode/issues/3)):
|
|
62
|
+
both protocols now fall back to saving the module to the controller's TEMP
|
|
63
|
+
volume, reading it, and deleting it. On RWS 1.0 the save round-trip is the
|
|
64
|
+
primary path so a stale `$HOME` copy can never shadow program memory.
|
|
65
|
+
Wire recipes live-verified on RW 6.16 + RW 7.21 (no mastership needed).
|
|
66
|
+
- **CFG writes were broken on both protocols**: `setCfgInstance` POSTed a
|
|
67
|
+
non-existent RWS 2.0 path (missing `/instances/`) and sent plain values where
|
|
68
|
+
RobotWare 7 requires the bracket representation (`Attr=[value,1]`);
|
|
69
|
+
`createCfgInstance` used an endpoint that does not exist (`…/{i}/create`) —
|
|
70
|
+
the real flow is `instances/create-default` + set. RWS 1.0 had no CFG write
|
|
71
|
+
support at all; the adapter now implements set/create/remove with the
|
|
72
|
+
plain-value forms. Full create → set → readback → delete cycles live-verified
|
|
73
|
+
on both controllers.
|
|
74
|
+
- **`probeProtocol` no longer misdetects arbitrary web servers as RWS 2.0** —
|
|
75
|
+
classification now requires a Digest (RWS 1.0) or Basic (RWS 2.0) challenge;
|
|
76
|
+
Bearer challenges and plain 200 responses are rejected.
|
|
77
|
+
- **`createAdapter` gained the Default-User fallback** `createClient` already
|
|
78
|
+
had — and the fallback predicate now keys on the typed `AUTH_FAILED` error
|
|
79
|
+
code (the old message regex never matched RWS 1.0 login failures).
|
|
80
|
+
- **`connect()`/`disconnect()` races**: disconnecting during an in-flight
|
|
81
|
+
connect no longer resurrects timers or subscriptions; a second `connect()`
|
|
82
|
+
with different host/credentials supersedes the in-flight attempt instead of
|
|
83
|
+
being silently coalesced; a poll that loses the race with `disconnect()` can
|
|
84
|
+
no longer write stale task state back into a cleared manager.
|
|
85
|
+
- **`writeSignal` with unknown network/device** now throws a descriptive
|
|
86
|
+
`RwsError` instead of firing a malformed `/signals///…` request.
|
|
87
|
+
- **Fileservice paths are percent-encoded per segment on both protocols** —
|
|
88
|
+
file names containing space, `#`, or `%` no longer break (or truncate at the
|
|
89
|
+
`#`) list/read/upload/delete/copy operations. `$HOME`/`$TEMP` prefixes stay
|
|
90
|
+
literal.
|
|
91
|
+
- **`RWS1Adapter.listMechunits` returns the controller's real mechunit list**
|
|
92
|
+
instead of a hardcoded `['ROB_1']`.
|
|
93
|
+
- **Session-cookie store writes are atomic** (temp file + rename), so
|
|
94
|
+
concurrent connects can't drop each other's entries.
|
|
95
|
+
- **Digest `qop=auth-int`** is now rejected with a clear error instead of
|
|
96
|
+
silently hashing as `auth` and failing authentication downstream.
|
|
97
|
+
- **Class-name minification safety**: protocol detection for config
|
|
98
|
+
persistence uses `instanceof`, not `constructor.name`.
|
|
99
|
+
|
|
100
|
+
### Added
|
|
101
|
+
|
|
102
|
+
- **`RobotManagerOptions`** — `new RobotManager({ refreshIntervalMs, strictTls })`
|
|
103
|
+
and `MultiRobotManager.fromConfigs(configs, options)`:
|
|
104
|
+
- `refreshIntervalMs` (default 1000, min 200) controls the polling cadence;
|
|
105
|
+
the subscription-active slow poll scales at 5×.
|
|
106
|
+
- `strictTls` (default false) turns real TLS certificate verification on for
|
|
107
|
+
controllers with proper certificates. Off by default because controllers
|
|
108
|
+
ship self-signed certs.
|
|
109
|
+
- **`RwsClient2` constructor options** `{ timeout, rejectUnauthorized }` — the
|
|
110
|
+
per-request timeout is finally configurable (was hardcoded 10 s), and callers
|
|
111
|
+
can opt into certificate verification.
|
|
112
|
+
- **`onLost` subscription callback** on the adapter `subscribe` surface —
|
|
113
|
+
invoked once when the event stream is terminally lost so callers can degrade
|
|
114
|
+
gracefully.
|
|
115
|
+
- **CI workflow** (Node 18/20/22 matrix: build, tests, lint) and `SECURITY.md`.
|
|
116
|
+
|
|
117
|
+
## [0.7.3] — 2026-07-03
|
|
118
|
+
|
|
119
|
+
### Fixed
|
|
120
|
+
|
|
121
|
+
- **TLS bypass now applied per-request, not only on the HTTP agent** — fixes
|
|
122
|
+
connections to real controllers from inside VS Code
|
|
123
|
+
([abb-rws-vscode#2](https://github.com/ichbinmeraj/abb-rws-vscode/issues/2)).
|
|
124
|
+
VS Code's extension host patches Node's `http`/`https` modules and replaces
|
|
125
|
+
custom agents for non-localhost targets, which silently dropped the
|
|
126
|
+
agent-level `rejectUnauthorized: false` and re-enabled certificate
|
|
127
|
+
verification — every real OmniCore (self-signed cert) then failed with
|
|
128
|
+
`self signed certificate`. Localhost VCs were never affected because the
|
|
129
|
+
extension host doesn't intercept localhost traffic. The setting is now also
|
|
130
|
+
set on each request's options in `RwsClient2.req()`, the subscription POST,
|
|
131
|
+
`RobotManager` port probing, and `detect.probeProtocol()`.
|
|
132
|
+
- **Examples 05 & 06 rewritten against the real API** — they previously used an
|
|
133
|
+
options-object `RwsClient2` constructor and `RobotManager` methods that never
|
|
134
|
+
existed. Both now use `new RobotManager()` + `connect(host, user, pass, port?)`.
|
|
135
|
+
- **`repository`/`homepage`** now point at the actual GitHub org (`ichbinmeraj`);
|
|
136
|
+
the previous links 404'd from the npm package page.
|
|
137
|
+
- Stale `types.ts` header no longer claims the package is RWS 1.0-only.
|
|
138
|
+
|
|
139
|
+
### Added
|
|
140
|
+
|
|
141
|
+
- **`prepack` guard** — `npm pack`/`npm publish` now runs the build and the full
|
|
142
|
+
116-test suite first, so a stale or broken artifact cannot be packed.
|
|
143
|
+
|
|
144
|
+
## [0.7.2] — 2026-05-08
|
|
145
|
+
|
|
146
|
+
### Documentation
|
|
147
|
+
|
|
148
|
+
- **Fixed the embarrassing compatibility table** that incorrectly claimed
|
|
149
|
+
RobotWare 7.x / OmniCore was "Not compatible". The package has supported
|
|
150
|
+
both protocols since v0.7.0 — the table was a stale leftover from when
|
|
151
|
+
only RWS 1.0 shipped. Now correctly shows both ✅, with live-tested
|
|
152
|
+
versions called out (RW7.21 + RW6.16).
|
|
153
|
+
- **Documented `RobotManager` higher-level surface** — the README's
|
|
154
|
+
API-reference tables only covered the protocol-level `RwsClient`, so
|
|
155
|
+
callers couldn't see what `RobotManager` adds on top: RMMP, mastership
|
|
156
|
+
status, opmode auto-routing, backup, FK, service-routine call,
|
|
157
|
+
tool/wobj activation, CFG write surface, DIPC messaging, file volumes,
|
|
158
|
+
module source, compress, value validation. New section lists all of
|
|
159
|
+
these with descriptions.
|
|
160
|
+
- **Clarified RWS 2.0 subscription quirks** — the polling-fallback
|
|
161
|
+
paragraph now names the specific RWS 2.0 VC `robapi2_subscription`
|
|
162
|
+
rejection that triggers the fallback, rather than implying it's
|
|
163
|
+
always 5s polling.
|
|
164
|
+
- **Session-pool clarifications** — IRC5's 70-session number was being
|
|
165
|
+
presented as universal; called out OmniCore's also-finite-but-different
|
|
166
|
+
pool with the empirical 503-once-full behaviour we observed during
|
|
167
|
+
protocol probes.
|
|
168
|
+
- **`RobotManager` polling cadence** correctly described as hybrid:
|
|
169
|
+
5 s when WS subscriptions handle state changes, 1 s when polling
|
|
170
|
+
covers everything.
|
|
171
|
+
|
|
172
|
+
No code changes — pure README updates. `RwsClient`, `RwsClient2`,
|
|
173
|
+
`RobotManager`, `MultiRobotManager`, `createClient`, all examples and
|
|
174
|
+
unit tests are bit-for-bit identical to v0.7.1.
|
|
175
|
+
|
|
7
176
|
## [0.7.1] — 2026-05-07
|
|
8
177
|
|
|
9
178
|
### Added
|
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ Prefer a GUI? The companion VS Code extension gives you live status, motion data
|
|
|
25
25
|
- **Connection lifecycle** — `RobotManager` handles port discovery, polling, WebSocket subscriptions with polling fallback, reconnect-on-failure
|
|
26
26
|
- **Typed adapter pattern** — `IRWSAdapter` lets you write code that works across both protocols
|
|
27
27
|
- **WebSocket subscriptions** for real-time events (panel state, RAPID exec, signals, persvar, elog, jointtarget, …)
|
|
28
|
-
- Session cookie management (avoids the controller's 70-session pool fill)
|
|
28
|
+
- Session cookie management (IRC5: avoids the controller's 70-session pool fill; OmniCore: avoids 503 lockout from session-pool exhaustion)
|
|
29
29
|
- Automatic `/logout` on disconnect to release server-side mastership and free the session slot
|
|
30
30
|
- Request rate limiting (< 20 req/sec)
|
|
31
31
|
- Fully typed public API — every method throws `RwsError` with a typed `code`
|
|
@@ -159,7 +159,9 @@ console.log('joints:', await client.getJointPositions());
|
|
|
159
159
|
// (`/rw/rapid/symbol/{symburl}/data`); the method shape is the same.
|
|
160
160
|
const tool0 = await client.getRapidVariable('T_ROB1', 'BASE', 'tool0');
|
|
161
161
|
|
|
162
|
-
// WebSocket subscriptions over `
|
|
162
|
+
// WebSocket subscriptions over the `rws_subscription` subprotocol
|
|
163
|
+
// (RWS 1.0 uses `robapi2_subscription` — the names are NOT interchangeable:
|
|
164
|
+
// RobotWare 7 rejects the 1.0 name with HTTP 400).
|
|
163
165
|
const unsubscribe = await client.subscribe(
|
|
164
166
|
['controllerstate', 'execution'],
|
|
165
167
|
(event) => console.log(event.resource, '=', event.value),
|
|
@@ -180,8 +182,10 @@ These are documented because they bite anyone who tries to write an RWS 2.0 clie
|
|
|
180
182
|
- **File service home** is `'HOME'`, not `'$HOME'`.
|
|
181
183
|
- **Symbol API path is suffix-style** — `/rw/rapid/symbol/{symburl}/data` (RWS 1.0 puts `/data` at the front).
|
|
182
184
|
- **Module unload** is `POST /rw/rapid/tasks/{task}/unloadmod` with body, NOT `DELETE` on the module URL (returns 405).
|
|
183
|
-
- **Self-signed TLS**
|
|
184
|
-
|
|
185
|
+
- **Self-signed TLS** everywhere — controllers ship self-signed certs, so certificate
|
|
186
|
+
verification is off by default. Pass `strictTls: true` (`RobotManagerOptions`) or
|
|
187
|
+
`rejectUnauthorized: true` (`RwsClient2` options) if your plant installed real certs.
|
|
188
|
+
- **WebSocket subscription URL** comes from the `Location` header (real hardware) or the XHTML body (VC). Subprotocol: `rws_subscription` (RWS 2.0) / `robapi2_subscription` (RWS 1.0).
|
|
185
189
|
|
|
186
190
|
---
|
|
187
191
|
|
|
@@ -195,7 +199,10 @@ import { MultiRobotManager } from 'abb-rws-client';
|
|
|
195
199
|
const multi = MultiRobotManager.fromConfigs([
|
|
196
200
|
{ id: 'cell-A', name: 'Cell A IRB120', host: '192.168.125.1', port: 80, useHttps: false, username: 'Admin', password: 'robotics' },
|
|
197
201
|
{ id: 'cell-B', name: 'Cell B IRB1200', host: '192.168.125.2', port: 443, useHttps: true, username: 'Admin', password: 'robotics' },
|
|
198
|
-
]
|
|
202
|
+
], {
|
|
203
|
+
refreshIntervalMs: 1000, // polling cadence (min 200); slow poll scales at 5×
|
|
204
|
+
strictTls: false, // true = verify controller TLS certificates
|
|
205
|
+
});
|
|
199
206
|
|
|
200
207
|
multi.onError((msg, actions) => {
|
|
201
208
|
console.error(`Robot error: ${msg}`);
|
|
@@ -214,17 +221,42 @@ multi.setActive('cell-B');
|
|
|
214
221
|
```
|
|
215
222
|
|
|
216
223
|
`MultiRobotManager` wraps individual `RobotManager` instances. Each `RobotManager` handles its own:
|
|
217
|
-
- Auto port discovery (probes 5466/9403/443/80/11811, plus a wide-scan fallback)
|
|
218
|
-
- Protocol auto-detection (Digest
|
|
219
|
-
- 5-
|
|
220
|
-
- WebSocket subscriptions with automatic polling
|
|
221
|
-
- Reconnect-on-failure (3-strike)
|
|
222
|
-
- Clean
|
|
224
|
+
- Auto port discovery (probes 5466 / 9403 / 443 / 80 / 11811 in that order, plus a wide-scan fallback when none of those answer — RobotStudio assigns random VC ports above 30000)
|
|
225
|
+
- Protocol auto-detection (`WWW-Authenticate: Digest` → RWS 1.0; `Basic` → RWS 2.0)
|
|
226
|
+
- Hybrid polling cadence: **5× the refresh interval when WebSocket subscriptions are active** (positions only — state-change resources stream over WS); **the plain refresh interval when subscriptions are unavailable** (full state coverage via polling). Default 1 s / 5 s, configurable via `refreshIntervalMs`.
|
|
227
|
+
- WebSocket subscriptions on both protocols, with dropped-socket auto-reconnect and automatic degradation to fast polling if the event stream is terminally lost
|
|
228
|
+
- Reconnect-on-failure (3-strike, surfaces via `onError` listener)
|
|
229
|
+
- Clean `GET /logout` on disconnect — releases server-side mastership and frees the session slot
|
|
223
230
|
|
|
224
231
|
You can also create a `RobotManager` directly if you only have one robot.
|
|
225
232
|
|
|
226
233
|
---
|
|
227
234
|
|
|
235
|
+
## `RobotManager` — higher-level surface
|
|
236
|
+
|
|
237
|
+
`RobotManager` wraps either client with operational helpers that handle mastership, polling, and protocol differences for you. In addition to delegating every protocol method to the underlying client, it exposes:
|
|
238
|
+
|
|
239
|
+
- **`getRmmpPrivilege()`**, **`requestRmmp(level)`** — Remote Mastership Privilege management. Required on OmniCore in AUTO mode for any modify op.
|
|
240
|
+
- **`getMastershipStatus(domain?)`** — read who currently holds rapid/cfg/motion mastership (uid + application name).
|
|
241
|
+
- **`setOperationMode('AUTO' | 'MANR' | 'MANF')`** — VC-only switch with auto-routing through MANR for AUTO ↔ MANF (the controller rejects direct transitions). Acquires `edit` mastership for the higher-privilege direction.
|
|
242
|
+
- **`setSpeedRatio(0..100)`** — wraps `edit` mastership; uses the live-verified `?action=setspeedratio&speed-ratio=N` form on RWS 2.0 (the bare endpoint returns 400).
|
|
243
|
+
- **`createBackup(name)`**, **`restoreBackup(name)`**, **`getBackupStatus()`**, **`listBackups()`** — `/ctrl/backup/...`
|
|
244
|
+
- **`callServiceRoutine(task, name, args?)`** — invoke a service routine remotely (calibration, brake check, etc.).
|
|
245
|
+
- **`calcJointsFromCartesian(...)`** — inverse kinematics. **`calcCartesianFromJoints(...)`** — forward kinematics.
|
|
246
|
+
- **`setActiveTool(mechunit, name)`**, **`setActiveWobj(mechunit, name)`** — switch active persistent tooldata / wobjdata.
|
|
247
|
+
- **CFG write** — `setCfgInstance` / `createCfgInstance` / `removeCfgInstance` / `loadCfgFile` / `saveCfgFile`, on **both** protocols (RWS 2.0 uses `instances/create-default` + the bracket value representation; RWS 1.0 uses the `?action=` forms — handled by the adapters). Each acquires the needed mastership for the duration.
|
|
248
|
+
- **DIPC** — `listDipcQueues` / `createDipcQueue` / `sendDipcMessage` / `readDipcMessage` / `removeDipcQueue`. Bidirectional messaging between RAPID and external clients.
|
|
249
|
+
- **`listFileVolumes()`** — every controller volume (HOME, BACKUP, DATA, ADDINDATA, PRODUCTS, RAMDISK, TEMP).
|
|
250
|
+
- **`getModuleSource(task, name)`** — pull a module's RAPID text in one call. Works even when the module has no backing file in `HOME` (loaded from `.pgf`/RobotStudio/pendant): the client saves it to the controller's TEMP volume, reads it, and deletes it.
|
|
251
|
+
- **`compressPath(source, dest)`** — controller-side compression.
|
|
252
|
+
- **`validateRapidValue(task, value, datatype)`** — pre-flight a literal before writing.
|
|
253
|
+
- **`RobotManager.discoverControllersMdns(opts?)`** *(static)* — find every ABB controller (real or virtual) announcing on the local network via mDNS/Bonjour. Returns system name, host, RWS port, RobotWare version, system GUID, and a `'rws1' | 'rws2'` classification — no port scanning needed.
|
|
254
|
+
- **Simulation panel** *(RWS 2.0 virtual controllers only)* — `simEmergencyStop()`, `simResetEmergencyStop()`, `simGeneralStop(engage?)`, `simAutoStop(engage?)`, `simEnableSwitch(on)`, `teleportMechunit(mechunit, joints, extJoints?)`. Drive the E-stop / guard-stop chain and reposition the simulated robot from the API. Throw `RwsError` on real hardware and RW6 (VC-only endpoints).
|
|
255
|
+
|
|
256
|
+
Every method returns `Promise<...>` and throws `RwsError` on failure.
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
228
260
|
## Logging
|
|
229
261
|
|
|
230
262
|
The lib ships a no-op logger by default. Hosts (CLIs, services, the VS Code extension) install their own:
|
|
@@ -460,7 +492,7 @@ try {
|
|
|
460
492
|
|
|
461
493
|
## Session Persistence
|
|
462
494
|
|
|
463
|
-
|
|
495
|
+
IRC5 controllers allow a maximum of **70 concurrent RWS sessions**; OmniCore controllers also have a finite session pool (the exact number isn't published, but heavy probing without `/logout` returns HTTP 503 once it fills). Persist the session cookie across restarts to always reuse the same slot — the lib calls `/logout` on `disconnect()` to free the slot cleanly, but a saved cookie is the most robust path:
|
|
464
496
|
|
|
465
497
|
```ts
|
|
466
498
|
import fs from 'fs';
|
|
@@ -481,14 +513,16 @@ await client.connect(); // reuses the existing session slot
|
|
|
481
513
|
|
|
482
514
|
## Rate Limits
|
|
483
515
|
|
|
484
|
-
| Constraint | Value |
|
|
485
|
-
|
|
486
|
-
| Max request rate | 20 req/sec |
|
|
487
|
-
| Client
|
|
488
|
-
| Max concurrent sessions | 70 |
|
|
489
|
-
| Session inactivity timeout | 5 minutes |
|
|
490
|
-
| Max session lifetime | 25 minutes |
|
|
491
|
-
| Max file upload | 800 MB |
|
|
516
|
+
| Constraint | Value | Source |
|
|
517
|
+
|------------|-------|--------|
|
|
518
|
+
| Max request rate | 20 req/sec | Both protocols (controller-enforced) |
|
|
519
|
+
| Client-enforced interval | 55 ms between requests | This package, default |
|
|
520
|
+
| Max concurrent sessions | 70 (IRC5) / finite (OmniCore) | Controller-enforced |
|
|
521
|
+
| Session inactivity timeout | 5 minutes (IRC5) | Controller-enforced |
|
|
522
|
+
| Max session lifetime | 25 minutes (IRC5) | Controller-enforced |
|
|
523
|
+
| Max file upload | 800 MB (IRC5) | Controller-enforced; OmniCore is similar |
|
|
524
|
+
|
|
525
|
+
OmniCore-specific limits aren't fully documented by ABB; what the lib observes empirically matches the IRC5 numbers within an order of magnitude.
|
|
492
526
|
|
|
493
527
|
---
|
|
494
528
|
|
|
@@ -496,10 +530,13 @@ await client.connect(); // reuses the existing session slot
|
|
|
496
530
|
|
|
497
531
|
| | RobotWare 6.x (RWS 1.0) | RobotWare 7.x (RWS 2.0) |
|
|
498
532
|
|--|--|--|
|
|
499
|
-
| This package | ✅
|
|
500
|
-
| IRC5 controller | ✅ | n/a |
|
|
501
|
-
| OmniCore controller | n/a |
|
|
502
|
-
| RobotStudio virtual | ✅ (
|
|
533
|
+
| This package | ✅ `RwsClient` | ✅ `RwsClient2` |
|
|
534
|
+
| IRC5 controller (real) | ✅ | n/a |
|
|
535
|
+
| OmniCore controller (real) | n/a | ✅ |
|
|
536
|
+
| RobotStudio virtual controller | ✅ (RW6.x VC) | ✅ (RW7.x VC) |
|
|
537
|
+
| Auto-detect from one entry point | ✅ via `createClient()` | ✅ via `createClient()` |
|
|
538
|
+
|
|
539
|
+
**Live-tested matrix** as of v0.7.1: RobotWare 7.21 (OmniCore VC, RWS 2.0), RobotWare 6.16 (IRC5 VC, RWS 1.0). 116 unit tests + 339 live protocol-coverage tests pass against both.
|
|
503
540
|
|
|
504
541
|
---
|
|
505
542
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parser for RWS 2.0 HAL JSON responses (`application/hal+json;v=2.0`) — the
|
|
3
|
+
* officially supported primary GET representation on OmniCore controllers.
|
|
4
|
+
*
|
|
5
|
+
* Live-verified 2026-07-09 on OmniCore VC RW7.21 across every GET endpoint
|
|
6
|
+
* family the client touches (panel, rapid, motionsystem, system, ctrl, elog,
|
|
7
|
+
* iosystem, cfg, mastership, users/rmmp). The wire shape is:
|
|
8
|
+
*
|
|
9
|
+
* { "_links": { "base": {...}, "self": {...} [, "next": {...}] },
|
|
10
|
+
* "status": { "code": 294912 }, // negative on errors, plus "msg"
|
|
11
|
+
* "state": [ { "_type": "...", "_title": "...", ...fields } ],
|
|
12
|
+
* "_embedded": { "resources": [ { "_type": "...", ...fields } ] } }
|
|
13
|
+
*
|
|
14
|
+
* `_type` carries the same identifier the XHTML representation puts in the
|
|
15
|
+
* `<li class="...">` attribute, `_title` mirrors the `title` attribute, and
|
|
16
|
+
* the former `<span class="X">` fields become plain JSON keys. Resources can
|
|
17
|
+
* nest (e.g. `sys-options-li` holds an `options` array of typed objects,
|
|
18
|
+
* `cfg-dt-instance-li` an `attrib` array of `cfg-ia-t` entries), so lookups
|
|
19
|
+
* recurse — mirroring how the XHTML parser finds `<li>` anywhere in the
|
|
20
|
+
* document. Presents the same read interface as `XhtmlParser` so `RwsClient2`
|
|
21
|
+
* can treat both representations alike.
|
|
22
|
+
*/
|
|
23
|
+
export declare class HalJsonParser {
|
|
24
|
+
private readonly root;
|
|
25
|
+
constructor(body: string);
|
|
26
|
+
/** Cheap sniff used to pick a parser: HAL bodies are single JSON objects. */
|
|
27
|
+
static looksLikeJson(body: string): boolean;
|
|
28
|
+
/** Returns the field map of the first resource whose `_type` matches. */
|
|
29
|
+
getState(type: string): Record<string, string>;
|
|
30
|
+
/** Returns field maps for every resource with a matching `_type`, in document order. */
|
|
31
|
+
getAllStates(type: string): Array<Record<string, string>>;
|
|
32
|
+
/** First scalar value stored under `key` in any resource (XhtmlParser.get analogue). */
|
|
33
|
+
get(key: string): string | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* Error details from the top-level status block. Success responses carry a
|
|
36
|
+
* positive code (294912 observed live); errors a negative code plus `msg`.
|
|
37
|
+
*/
|
|
38
|
+
getError(): {
|
|
39
|
+
code: string;
|
|
40
|
+
msg: string;
|
|
41
|
+
} | null;
|
|
42
|
+
/**
|
|
43
|
+
* Pagination link (`_links.next.href`), raw as sent. Live-verified quirk:
|
|
44
|
+
* the controller XML-escapes ampersands even inside JSON strings
|
|
45
|
+
* (`"signals?start=3&limit=3"`) — callers must unescape, exactly as on
|
|
46
|
+
* the XHTML `rel="next"` path.
|
|
47
|
+
*/
|
|
48
|
+
nextHref(): string | undefined;
|
|
49
|
+
private rootObject;
|
|
50
|
+
/** Depth-first visit of every object in the tree, skipping `_links` blocks. */
|
|
51
|
+
private walk;
|
|
52
|
+
/** Scalar fields → strings; `_title` kept; self link exposed as `_href` (XHTML parity). */
|
|
53
|
+
private static flatten;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=HalJsonParser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HalJsonParser.d.ts","sourceRoot":"","sources":["../src/HalJsonParser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAKH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAkB;gBAE3B,IAAI,EAAE,MAAM;IAMxB,6EAA6E;IAC7E,MAAM,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI3C,yEAAyE;IACzE,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAI9C,wFAAwF;IACxF,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAQzD,wFAAwF;IACxF,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAYpC;;;OAGG;IACH,QAAQ,IAAI;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAShD;;;;;OAKG;IACH,QAAQ,IAAI,MAAM,GAAG,SAAS;IAS9B,OAAO,CAAC,UAAU;IAMlB,+EAA+E;IAC/E,OAAO,CAAC,IAAI;IAcZ,2FAA2F;IAC3F,OAAO,CAAC,MAAM,CAAC,OAAO;CAmBvB"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parser for RWS 2.0 HAL JSON responses (`application/hal+json;v=2.0`) — the
|
|
3
|
+
* officially supported primary GET representation on OmniCore controllers.
|
|
4
|
+
*
|
|
5
|
+
* Live-verified 2026-07-09 on OmniCore VC RW7.21 across every GET endpoint
|
|
6
|
+
* family the client touches (panel, rapid, motionsystem, system, ctrl, elog,
|
|
7
|
+
* iosystem, cfg, mastership, users/rmmp). The wire shape is:
|
|
8
|
+
*
|
|
9
|
+
* { "_links": { "base": {...}, "self": {...} [, "next": {...}] },
|
|
10
|
+
* "status": { "code": 294912 }, // negative on errors, plus "msg"
|
|
11
|
+
* "state": [ { "_type": "...", "_title": "...", ...fields } ],
|
|
12
|
+
* "_embedded": { "resources": [ { "_type": "...", ...fields } ] } }
|
|
13
|
+
*
|
|
14
|
+
* `_type` carries the same identifier the XHTML representation puts in the
|
|
15
|
+
* `<li class="...">` attribute, `_title` mirrors the `title` attribute, and
|
|
16
|
+
* the former `<span class="X">` fields become plain JSON keys. Resources can
|
|
17
|
+
* nest (e.g. `sys-options-li` holds an `options` array of typed objects,
|
|
18
|
+
* `cfg-dt-instance-li` an `attrib` array of `cfg-ia-t` entries), so lookups
|
|
19
|
+
* recurse — mirroring how the XHTML parser finds `<li>` anywhere in the
|
|
20
|
+
* document. Presents the same read interface as `XhtmlParser` so `RwsClient2`
|
|
21
|
+
* can treat both representations alike.
|
|
22
|
+
*/
|
|
23
|
+
export class HalJsonParser {
|
|
24
|
+
root;
|
|
25
|
+
constructor(body) {
|
|
26
|
+
let parsed = null;
|
|
27
|
+
try {
|
|
28
|
+
parsed = JSON.parse(body);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
parsed = null;
|
|
32
|
+
}
|
|
33
|
+
this.root = parsed;
|
|
34
|
+
}
|
|
35
|
+
/** Cheap sniff used to pick a parser: HAL bodies are single JSON objects. */
|
|
36
|
+
static looksLikeJson(body) {
|
|
37
|
+
return body.trimStart().startsWith('{');
|
|
38
|
+
}
|
|
39
|
+
/** Returns the field map of the first resource whose `_type` matches. */
|
|
40
|
+
getState(type) {
|
|
41
|
+
return this.getAllStates(type)[0] ?? {};
|
|
42
|
+
}
|
|
43
|
+
/** Returns field maps for every resource with a matching `_type`, in document order. */
|
|
44
|
+
getAllStates(type) {
|
|
45
|
+
const results = [];
|
|
46
|
+
this.walk(this.root, obj => {
|
|
47
|
+
if (obj['_type'] === type) {
|
|
48
|
+
results.push(HalJsonParser.flatten(obj));
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
return results;
|
|
52
|
+
}
|
|
53
|
+
/** First scalar value stored under `key` in any resource (XhtmlParser.get analogue). */
|
|
54
|
+
get(key) {
|
|
55
|
+
let found;
|
|
56
|
+
this.walk(this.root, obj => {
|
|
57
|
+
if (found !== undefined) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const v = obj[key];
|
|
61
|
+
if (v !== undefined && v !== null && typeof v !== 'object' && !key.startsWith('_')) {
|
|
62
|
+
found = String(v);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
return found;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Error details from the top-level status block. Success responses carry a
|
|
69
|
+
* positive code (294912 observed live); errors a negative code plus `msg`.
|
|
70
|
+
*/
|
|
71
|
+
getError() {
|
|
72
|
+
const status = this.rootObject()?.['status'];
|
|
73
|
+
if (!status || typeof status !== 'object' || Array.isArray(status)) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
const code = status['code'];
|
|
77
|
+
if (typeof code !== 'number' || code >= 0) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
const msg = status['msg'];
|
|
81
|
+
return { code: String(code), msg: typeof msg === 'string' ? msg : '' };
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Pagination link (`_links.next.href`), raw as sent. Live-verified quirk:
|
|
85
|
+
* the controller XML-escapes ampersands even inside JSON strings
|
|
86
|
+
* (`"signals?start=3&limit=3"`) — callers must unescape, exactly as on
|
|
87
|
+
* the XHTML `rel="next"` path.
|
|
88
|
+
*/
|
|
89
|
+
nextHref() {
|
|
90
|
+
const links = this.rootObject()?.['_links'];
|
|
91
|
+
if (!links || typeof links !== 'object' || Array.isArray(links)) {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
const next = links['next'];
|
|
95
|
+
if (!next || typeof next !== 'object' || Array.isArray(next)) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
const href = next['href'];
|
|
99
|
+
return typeof href === 'string' && href ? href : undefined;
|
|
100
|
+
}
|
|
101
|
+
rootObject() {
|
|
102
|
+
return this.root && typeof this.root === 'object' && !Array.isArray(this.root)
|
|
103
|
+
? this.root
|
|
104
|
+
: null;
|
|
105
|
+
}
|
|
106
|
+
/** Depth-first visit of every object in the tree, skipping `_links` blocks. */
|
|
107
|
+
walk(node, visit) {
|
|
108
|
+
if (Array.isArray(node)) {
|
|
109
|
+
for (const item of node) {
|
|
110
|
+
this.walk(item, visit);
|
|
111
|
+
}
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (node && typeof node === 'object') {
|
|
115
|
+
visit(node);
|
|
116
|
+
for (const [key, value] of Object.entries(node)) {
|
|
117
|
+
if (key === '_links') {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (value && typeof value === 'object') {
|
|
121
|
+
this.walk(value, visit);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** Scalar fields → strings; `_title` kept; self link exposed as `_href` (XHTML parity). */
|
|
127
|
+
static flatten(obj) {
|
|
128
|
+
const fields = {};
|
|
129
|
+
const title = obj['_title'];
|
|
130
|
+
if (typeof title === 'string') {
|
|
131
|
+
fields['_title'] = title;
|
|
132
|
+
}
|
|
133
|
+
const links = obj['_links'];
|
|
134
|
+
if (links && typeof links === 'object' && !Array.isArray(links)) {
|
|
135
|
+
const self = links['self'];
|
|
136
|
+
if (self && typeof self === 'object' && !Array.isArray(self)) {
|
|
137
|
+
const href = self['href'];
|
|
138
|
+
if (typeof href === 'string') {
|
|
139
|
+
fields['_href'] = href;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
144
|
+
if (key.startsWith('_')) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (value === null || typeof value === 'object') {
|
|
148
|
+
continue;
|
|
149
|
+
} // nested resources have their own _type
|
|
150
|
+
fields[key] = String(value);
|
|
151
|
+
}
|
|
152
|
+
return fields;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=HalJsonParser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HalJsonParser.js","sourceRoot":"","sources":["../src/HalJsonParser.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HttpSession.d.ts","sourceRoot":"","sources":["../src/HttpSession.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,KAAK,EAAmB,YAAY,EAAE,MAAM,YAAY,CAAC;AAchE,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,0FAA0F;IAC1F,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAID,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IAEnC,2DAA2D;IAC3D,OAAO,CAAC,OAAO,CAAkC;IAEjD,yDAAyD;IACzD,OAAO,CAAC,eAAe,CAAgC;IAEvD,sEAAsE;IACtE,OAAO,CAAC,UAAU,CAAK;IAEvB,gDAAgD;IAChD,OAAO,CAAC,eAAe,CAAK;IAE5B,iFAAiF;IACjF,OAAO,CAAC,gBAAgB,CAAK;IAE7B,0DAA0D;IAC1D,OAAO,CAAC,YAAY,CAAoC;IAExD,yDAAyD;IACzD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAiB;gBAE/C,OAAO,EAAE,kBAAkB;IAqBvC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAIxC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAIxD,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC;IAInE,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAI3C,yEAAyE;IACzE,eAAe,IAAI,MAAM;IAIzB,yFAAyF;IACzF,gBAAgB,IAAI,MAAM,GAAG,IAAI;IAKjC;;;;;;;;OAQG;IACH,YAAY,IAAI,IAAI;IAMpB;;;;OAIG;IACH,OAAO,CAAC,OAAO;YAsBD,OAAO;IA4ErB,iFAAiF;YACnE,QAAQ;IAoDtB;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IA8B5B;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,eAAe;
|
|
1
|
+
{"version":3,"file":"HttpSession.d.ts","sourceRoot":"","sources":["../src/HttpSession.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,KAAK,EAAmB,YAAY,EAAE,MAAM,YAAY,CAAC;AAchE,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,0FAA0F;IAC1F,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAID,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IAEnC,2DAA2D;IAC3D,OAAO,CAAC,OAAO,CAAkC;IAEjD,yDAAyD;IACzD,OAAO,CAAC,eAAe,CAAgC;IAEvD,sEAAsE;IACtE,OAAO,CAAC,UAAU,CAAK;IAEvB,gDAAgD;IAChD,OAAO,CAAC,eAAe,CAAK;IAE5B,iFAAiF;IACjF,OAAO,CAAC,gBAAgB,CAAK;IAE7B,0DAA0D;IAC1D,OAAO,CAAC,YAAY,CAAoC;IAExD,yDAAyD;IACzD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAiB;gBAE/C,OAAO,EAAE,kBAAkB;IAqBvC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAIxC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAIxD,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC;IAInE,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAI3C,yEAAyE;IACzE,eAAe,IAAI,MAAM;IAIzB,yFAAyF;IACzF,gBAAgB,IAAI,MAAM,GAAG,IAAI;IAKjC;;;;;;;;OAQG;IACH,YAAY,IAAI,IAAI;IAMpB;;;;OAIG;IACH,OAAO,CAAC,OAAO;YAsBD,OAAO;IA4ErB,iFAAiF;YACnE,QAAQ;IAoDtB;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IA8B5B;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,eAAe;IAwDvB;;;;;OAKG;IACH,OAAO,CAAC,YAAY;IAyBpB,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,gBAAgB;IASxB,OAAO,CAAC,IAAI;IAIZ,OAAO,CAAC,aAAa;CAetB"}
|
package/dist/HttpSession.js
CHANGED
|
@@ -267,12 +267,23 @@ export class HttpSession {
|
|
|
267
267
|
*/
|
|
268
268
|
buildAuthHeader(method, uri) {
|
|
269
269
|
const challenge = this.digestChallenge;
|
|
270
|
+
// qop may be a comma-separated list, e.g. qop="auth,auth-int". Pick auth when
|
|
271
|
+
// offered; auth-int (body-hash mode) is not implemented, so an auth-int-only
|
|
272
|
+
// challenge must fail loudly rather than answer with a plain-auth hash.
|
|
273
|
+
let useQopAuth = false;
|
|
274
|
+
if (challenge.qop) {
|
|
275
|
+
const offered = challenge.qop.split(',').map((q) => q.trim());
|
|
276
|
+
if (!offered.includes('auth')) {
|
|
277
|
+
throw new RwsError(`Digest qop "${challenge.qop}" is not supported — only qop=auth`, 'AUTH_FAILED');
|
|
278
|
+
}
|
|
279
|
+
useQopAuth = true;
|
|
280
|
+
}
|
|
270
281
|
const nc = (++this.nonceCount).toString(16).padStart(8, '0');
|
|
271
282
|
const cnonce = randomBytes(16).toString('hex');
|
|
272
283
|
const ha1 = md5(`${this.username}:${challenge.realm}:${this.password}`);
|
|
273
284
|
const ha2 = md5(`${method}:${uri}`);
|
|
274
285
|
let responseHash;
|
|
275
|
-
if (
|
|
286
|
+
if (useQopAuth) {
|
|
276
287
|
// RFC 2617 qop mode
|
|
277
288
|
responseHash = md5(`${ha1}:${challenge.nonce}:${nc}:${cnonce}:auth:${ha2}`);
|
|
278
289
|
}
|
|
@@ -290,7 +301,7 @@ export class HttpSession {
|
|
|
290
301
|
`cnonce="${cnonce}"`,
|
|
291
302
|
`response="${responseHash}"`,
|
|
292
303
|
];
|
|
293
|
-
if (
|
|
304
|
+
if (useQopAuth) {
|
|
294
305
|
parts.push(`qop=auth`);
|
|
295
306
|
}
|
|
296
307
|
if (challenge.opaque) {
|