abb-rws-client 0.1.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 +261 -0
- package/dist/HttpSession.d.ts +92 -0
- package/dist/HttpSession.d.ts.map +1 -0
- package/dist/HttpSession.js +321 -0
- package/dist/HttpSession.js.map +1 -0
- package/dist/ResourceMapper.d.ts +95 -0
- package/dist/ResourceMapper.d.ts.map +1 -0
- package/dist/ResourceMapper.js +146 -0
- package/dist/ResourceMapper.js.map +1 -0
- package/dist/ResponseParser.d.ts +73 -0
- package/dist/ResponseParser.d.ts.map +1 -0
- package/dist/ResponseParser.js +294 -0
- package/dist/ResponseParser.js.map +1 -0
- package/dist/RwsClient.d.ts +156 -0
- package/dist/RwsClient.d.ts.map +1 -0
- package/dist/RwsClient.js +353 -0
- package/dist/RwsClient.js.map +1 -0
- package/dist/WsSubscriber.d.ts +33 -0
- package/dist/WsSubscriber.d.ts.map +1 -0
- package/dist/WsSubscriber.js +234 -0
- package/dist/WsSubscriber.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +90 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +21 -0
- package/dist/types.js.map +1 -0
- package/package.json +33 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Meraj Safari
|
|
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,261 @@
|
|
|
1
|
+
# abb-rws-client
|
|
2
|
+
|
|
3
|
+
A typed TypeScript/Node.js HTTP and WebSocket client for **ABB IRC5 robot controllers** using [Robot Web Services (RWS) 1.0](https://developercenter.robotstudio.com/api/rwsApi/).
|
|
4
|
+
|
|
5
|
+
> **Compatibility:** RWS 1.0 / RobotWare 6.x only.
|
|
6
|
+
> **Not compatible** with OmniCore controllers, RobotWare 7.x, or RWS 2.0.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- HTTP Digest Authentication (RFC 2617) — no external auth libraries
|
|
13
|
+
- Session cookie management (`ABBCX`, `-http-session-`)
|
|
14
|
+
- Request rate limiting (< 20 req/sec, configurable)
|
|
15
|
+
- Automatic session re-authentication after 5-minute inactivity
|
|
16
|
+
- WebSocket subscriptions for real-time events (execution state, I/O signals, etc.)
|
|
17
|
+
- Auto-reconnect on WebSocket disconnect (3 retries, exponential backoff)
|
|
18
|
+
- Fully typed public API — every method throws `RwsError` with a typed `code`
|
|
19
|
+
- Zero runtime dependencies — Node 18+ built-ins only
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install abb-rws-client
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Requirements:** Node.js 21+ (or Node 18 with `--experimental-websocket` for WebSocket subscription support).
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Quick Start — RobotStudio virtual controller
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { RwsClient, RwsError } from 'abb-rws-client';
|
|
37
|
+
|
|
38
|
+
const client = new RwsClient({
|
|
39
|
+
host: '127.0.0.1', // RobotStudio default
|
|
40
|
+
port: 80,
|
|
41
|
+
username: 'Default User',
|
|
42
|
+
password: 'robotics',
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
await client.connect();
|
|
47
|
+
|
|
48
|
+
// Read state
|
|
49
|
+
const state = await client.getControllerState();
|
|
50
|
+
console.log('Controller state:', state); // e.g. 'motoron'
|
|
51
|
+
|
|
52
|
+
const mode = await client.getOperationMode();
|
|
53
|
+
console.log('Operation mode:', mode); // e.g. 'AUTO'
|
|
54
|
+
|
|
55
|
+
// Read positions
|
|
56
|
+
const joints = await client.getJointPositions();
|
|
57
|
+
console.log('J1:', joints.rax_1, 'degrees');
|
|
58
|
+
|
|
59
|
+
const tcp = await client.getCartesianPosition();
|
|
60
|
+
console.log('TCP:', tcp.x, tcp.y, tcp.z, 'mm');
|
|
61
|
+
|
|
62
|
+
// RAPID execution
|
|
63
|
+
await client.startRapid();
|
|
64
|
+
await client.stopRapid();
|
|
65
|
+
|
|
66
|
+
// I/O signals
|
|
67
|
+
const di = await client.readSignal('Local', 'DRV_1', 'DI_1');
|
|
68
|
+
console.log('DI_1 =', di.value);
|
|
69
|
+
await client.writeSignal('Local', 'DRV_1', 'DO_1', '1');
|
|
70
|
+
|
|
71
|
+
// Upload and load a RAPID module
|
|
72
|
+
const modSource = `MODULE MyMod\n PROC main()\n TPWrite "Hello";\n ENDPROC\nENDMODULE`;
|
|
73
|
+
await client.uploadModule('$HOME/MyMod.mod', modSource);
|
|
74
|
+
await client.loadModule('T_ROB1', '$HOME/MyMod.mod');
|
|
75
|
+
|
|
76
|
+
// Subscribe to real-time events
|
|
77
|
+
const unsubscribe = await client.subscribe(
|
|
78
|
+
['execution', 'controllerstate'],
|
|
79
|
+
(event) => {
|
|
80
|
+
console.log(`[${event.timestamp.toISOString()}] ${event.resource} = ${event.value}`);
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// ... later, unsubscribe and disconnect
|
|
85
|
+
await unsubscribe();
|
|
86
|
+
await client.disconnect();
|
|
87
|
+
|
|
88
|
+
} catch (e) {
|
|
89
|
+
if (e instanceof RwsError) {
|
|
90
|
+
console.error(`[${e.code}] ${e.message} (HTTP ${e.httpStatus ?? 'N/A'})`);
|
|
91
|
+
} else {
|
|
92
|
+
throw e;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## API Reference
|
|
100
|
+
|
|
101
|
+
### `new RwsClient(options)`
|
|
102
|
+
|
|
103
|
+
| Option | Type | Default | Description |
|
|
104
|
+
|---|---|---|---|
|
|
105
|
+
| `host` | `string` | — | Controller IP or hostname |
|
|
106
|
+
| `port` | `number` | `80` | HTTP port |
|
|
107
|
+
| `username` | `string` | `'Default User'` | RWS username |
|
|
108
|
+
| `password` | `string` | `'robotics'` | RWS password |
|
|
109
|
+
| `requestIntervalMs` | `number` | `55` | Minimum ms between requests (enforces < 20 req/sec) |
|
|
110
|
+
| `timeout` | `number` | `5000` | Request timeout in ms |
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
### Connection
|
|
115
|
+
|
|
116
|
+
| Method | Returns | Description |
|
|
117
|
+
|---|---|---|
|
|
118
|
+
| `connect()` | `Promise<void>` | Establish session and authenticate |
|
|
119
|
+
| `disconnect()` | `Promise<void>` | Close subscriptions and clear session |
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
### Controller State
|
|
124
|
+
|
|
125
|
+
| Method | Returns | Description |
|
|
126
|
+
|---|---|---|
|
|
127
|
+
| `getControllerState()` | `Promise<ControllerState>` | `'init'` \| `'motoroff'` \| `'motoron'` \| `'guardstop'` \| `'emergencystop'` \| `'emergencystopreset'` \| `'sysfail'` |
|
|
128
|
+
| `getOperationMode()` | `Promise<OperationMode>` | `'AUTO'` \| `'MANR'` \| `'MANF'` |
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
### RAPID Execution
|
|
133
|
+
|
|
134
|
+
| Method | Returns | Description |
|
|
135
|
+
|---|---|---|
|
|
136
|
+
| `getRapidExecutionState()` | `Promise<ExecutionState>` | `'running'` \| `'stopped'` |
|
|
137
|
+
| `getRapidTasks()` | `Promise<RapidTask[]>` | All RAPID tasks and their states |
|
|
138
|
+
| `startRapid()` | `Promise<void>` | Start RAPID execution (requires AUTO + motors on) |
|
|
139
|
+
| `stopRapid()` | `Promise<void>` | Stop RAPID execution |
|
|
140
|
+
| `resetRapid()` | `Promise<void>` | Reset program pointer to main |
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
### Motion
|
|
145
|
+
|
|
146
|
+
| Method | Returns | Description |
|
|
147
|
+
|---|---|---|
|
|
148
|
+
| `getJointPositions(mechunit?)` | `Promise<JointTarget>` | Joint angles in degrees for all 6 axes |
|
|
149
|
+
| `getCartesianPosition(mechunit?, tool?, wobj?)` | `Promise<RobTarget>` | TCP position (mm) and orientation (quaternion) |
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
### Modules
|
|
154
|
+
|
|
155
|
+
| Method | Returns | Description |
|
|
156
|
+
|---|---|---|
|
|
157
|
+
| `uploadModule(remotePath, content)` | `Promise<void>` | Upload RAPID `.mod` source to controller filesystem |
|
|
158
|
+
| `loadModule(taskName, modulePath)` | `Promise<void>` | Load an uploaded module into a RAPID task |
|
|
159
|
+
| `listModules(taskName)` | `Promise<string[]>` | Names of all loaded modules in a task |
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
### I/O Signals
|
|
164
|
+
|
|
165
|
+
| Method | Returns | Description |
|
|
166
|
+
|---|---|---|
|
|
167
|
+
| `readSignal(network, device, name)` | `Promise<Signal>` | Read current signal value |
|
|
168
|
+
| `writeSignal(network, device, name, value)` | `Promise<void>` | Write a value to an output signal |
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
### Subscriptions
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
subscribe(
|
|
176
|
+
resources: SubscriptionResource[],
|
|
177
|
+
handler: (event: SubscriptionEvent) => void
|
|
178
|
+
): Promise<() => Promise<void>>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Subscribe to real-time RWS events. Returns an async unsubscribe function.
|
|
182
|
+
|
|
183
|
+
**`SubscriptionResource`** can be:
|
|
184
|
+
- `'execution'` — RAPID execution state changes
|
|
185
|
+
- `'controllerstate'` — controller state changes
|
|
186
|
+
- `'operationmode'` — operation mode changes
|
|
187
|
+
- `{ type: 'signal'; name: 'network/device/signalname' }` — I/O signal changes
|
|
188
|
+
- `{ type: 'persvar'; name: 'task/varname' }` — RAPID persistent variable changes
|
|
189
|
+
|
|
190
|
+
**`SubscriptionEvent`**:
|
|
191
|
+
```ts
|
|
192
|
+
{
|
|
193
|
+
resource: string; // RWS path of the changed resource
|
|
194
|
+
value: string; // New value as string
|
|
195
|
+
timestamp: Date; // When the event was received
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
### Error Handling
|
|
202
|
+
|
|
203
|
+
All public methods throw `RwsError` (never plain `Error`) with a typed `code`:
|
|
204
|
+
|
|
205
|
+
| Code | Meaning |
|
|
206
|
+
|---|---|
|
|
207
|
+
| `'AUTH_FAILED'` | Wrong credentials or session rejected |
|
|
208
|
+
| `'SESSION_EXPIRED'` | Session timed out and re-auth was not possible |
|
|
209
|
+
| `'MOTORS_OFF'` | Action requires motors to be on |
|
|
210
|
+
| `'MODULE_NOT_FOUND'` | Module file not found on controller filesystem |
|
|
211
|
+
| `'CONTROLLER_BUSY'` | Controller returned 503; retry later |
|
|
212
|
+
| `'RATE_LIMITED'` | Too many requests (429) |
|
|
213
|
+
| `'NETWORK_ERROR'` | TCP/timeout/WebSocket error |
|
|
214
|
+
| `'PARSE_ERROR'` | Unexpected XML response format |
|
|
215
|
+
| `'UNKNOWN'` | Unmapped error; check `httpStatus` and `rwsDetail` |
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
try {
|
|
219
|
+
await client.startRapid();
|
|
220
|
+
} catch (e) {
|
|
221
|
+
if (e instanceof RwsError) {
|
|
222
|
+
switch (e.code) {
|
|
223
|
+
case 'MOTORS_OFF':
|
|
224
|
+
console.error('Enable motors on the FlexPendant first');
|
|
225
|
+
break;
|
|
226
|
+
case 'AUTH_FAILED':
|
|
227
|
+
console.error('Check username/password');
|
|
228
|
+
break;
|
|
229
|
+
default:
|
|
230
|
+
console.error(`Unexpected error [${e.code}]: ${e.message}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Compatibility
|
|
239
|
+
|
|
240
|
+
| Feature | RobotWare 6.x (RWS 1.0) | RobotWare 7.x (RWS 2.0) |
|
|
241
|
+
|---|---|---|
|
|
242
|
+
| This package | ✓ Supported | ✗ Not compatible |
|
|
243
|
+
| Controller: IRC5 | ✓ | n/a |
|
|
244
|
+
| Controller: OmniCore | n/a | ✗ |
|
|
245
|
+
| RobotStudio (virtual) | ✓ (127.0.0.1) | ✗ |
|
|
246
|
+
|
|
247
|
+
RWS 2.0 uses a completely different API structure. This package implements RWS 1.0 only and will not function with RobotWare 7.x or OmniCore controllers.
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## Resources
|
|
252
|
+
|
|
253
|
+
- [ABB RWS 1.0 API Reference](https://developercenter.robotstudio.com/api/rwsApi/)
|
|
254
|
+
- [ABB Developer Center](https://developercenter.robotstudio.com/api/RWS)
|
|
255
|
+
- [VALIDATION.md](./VALIDATION.md) — Manual test procedures against a real IRC5 / RobotStudio controller
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## License
|
|
260
|
+
|
|
261
|
+
MIT — see [LICENSE](./LICENSE)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HttpSession — HTTP communication layer for ABB IRC5 controllers.
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - HTTP Digest Authentication (RFC 2617) implemented from scratch using node:crypto
|
|
6
|
+
* - ABBCX + -http-session- cookie management
|
|
7
|
+
* - Request queue enforcing minimum interval between requests (<20 req/sec RWS limit)
|
|
8
|
+
* - Automatic re-authentication on session expiry (5-minute inactivity)
|
|
9
|
+
* - 401 retry with fresh digest handshake; 503 retry with 200ms backoff
|
|
10
|
+
* - AbortController-based timeout
|
|
11
|
+
*
|
|
12
|
+
* Uses Node 18+ built-in fetch and node:crypto. Zero external dependencies.
|
|
13
|
+
*/
|
|
14
|
+
import type { HttpResponse } from './types.js';
|
|
15
|
+
export interface HttpSessionOptions {
|
|
16
|
+
baseUrl: string;
|
|
17
|
+
username: string;
|
|
18
|
+
password: string;
|
|
19
|
+
requestIntervalMs: number;
|
|
20
|
+
timeoutMs: number;
|
|
21
|
+
}
|
|
22
|
+
export declare class HttpSession {
|
|
23
|
+
private readonly baseUrl;
|
|
24
|
+
private readonly username;
|
|
25
|
+
private readonly password;
|
|
26
|
+
private readonly requestIntervalMs;
|
|
27
|
+
private readonly timeoutMs;
|
|
28
|
+
/** Stored session cookies: '-http-session-' and 'ABBCX' */
|
|
29
|
+
private cookies;
|
|
30
|
+
/** Last parsed digest challenge from WWW-Authenticate */
|
|
31
|
+
private digestChallenge;
|
|
32
|
+
/** Nonce use counter — reset to 0 whenever a new nonce is received */
|
|
33
|
+
private nonceCount;
|
|
34
|
+
/** Timestamp of the most recent request sent */
|
|
35
|
+
private lastRequestTime;
|
|
36
|
+
/** Timestamp of the most recent successful response — used for session expiry */
|
|
37
|
+
private lastActivityTime;
|
|
38
|
+
/** Promise chain that serialises all outbound requests */
|
|
39
|
+
private requestQueue;
|
|
40
|
+
/** 5-minute session inactivity timeout (milliseconds) */
|
|
41
|
+
private static readonly SESSION_TIMEOUT_MS;
|
|
42
|
+
constructor(options: HttpSessionOptions);
|
|
43
|
+
get(path: string): Promise<HttpResponse>;
|
|
44
|
+
post(path: string, body?: string): Promise<HttpResponse>;
|
|
45
|
+
put(path: string, body: string | Uint8Array): Promise<HttpResponse>;
|
|
46
|
+
delete(path: string): Promise<HttpResponse>;
|
|
47
|
+
/** Returns the current cookie string for use in WebSocket connections */
|
|
48
|
+
getCookieHeader(): string;
|
|
49
|
+
/** Clear all session state (called on disconnect) */
|
|
50
|
+
clearSession(): void;
|
|
51
|
+
/**
|
|
52
|
+
* Append a function to the serial request queue, enforcing the minimum interval
|
|
53
|
+
* between requests. Error suppression on `this.requestQueue` (not on `result`)
|
|
54
|
+
* ensures queue continues processing even when individual requests fail.
|
|
55
|
+
*/
|
|
56
|
+
private enqueue;
|
|
57
|
+
private execute;
|
|
58
|
+
/** Issue a single HTTP request with digest auth header if we have a challenge */
|
|
59
|
+
private rawFetch;
|
|
60
|
+
/**
|
|
61
|
+
* Parse the WWW-Authenticate: Digest ... header into a DigestChallenge.
|
|
62
|
+
* Handles both quoted and unquoted parameter values per RFC 2617.
|
|
63
|
+
*/
|
|
64
|
+
private parseDigestChallenge;
|
|
65
|
+
/**
|
|
66
|
+
* Build the Authorization: Digest ... header value for the given request.
|
|
67
|
+
* Increments the nonce use counter (nc).
|
|
68
|
+
*
|
|
69
|
+
* RFC 2617 §3.2.2:
|
|
70
|
+
* HA1 = MD5(username:realm:password)
|
|
71
|
+
* HA2 = MD5(method:digestURI)
|
|
72
|
+
* response = MD5(HA1:nonce:nc:cnonce:qop:HA2) — when qop=auth
|
|
73
|
+
* response = MD5(HA1:nonce:HA2) — RFC 2069 compat (no qop)
|
|
74
|
+
*
|
|
75
|
+
* Important: the space in 'Default User' is NOT percent-encoded for HA1.
|
|
76
|
+
* The nc value is NOT quoted in the Authorization header.
|
|
77
|
+
* The URI is path+query only, not scheme://host:port/path.
|
|
78
|
+
*/
|
|
79
|
+
private buildAuthHeader;
|
|
80
|
+
/**
|
|
81
|
+
* Extract Set-Cookie headers from a response and store name=value pairs.
|
|
82
|
+
* Uses Headers.getSetCookie() (Node 18.14.1+) to correctly handle multiple
|
|
83
|
+
* Set-Cookie headers. Falls back to headers.get('set-cookie') on older Node 18,
|
|
84
|
+
* though this may misparse cookie values containing commas.
|
|
85
|
+
*/
|
|
86
|
+
private storeCookies;
|
|
87
|
+
private buildCookieHeader;
|
|
88
|
+
private isSessionExpired;
|
|
89
|
+
private isOk;
|
|
90
|
+
private mapHttpStatus;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=HttpSession.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HttpSession.d.ts","sourceRoot":"","sources":["../src/HttpSession.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,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;CACnB;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;IAUvC,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,qDAAqD;IACrD,YAAY,IAAI,IAAI;IASpB;;;;OAIG;IACH,OAAO,CAAC,OAAO;YAsBD,OAAO;IA0DrB,iFAAiF;YACnE,QAAQ;IAoDtB;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IA8B5B;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,eAAe;IAwCvB;;;;;OAKG;IACH,OAAO,CAAC,YAAY;IAyBpB,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,gBAAgB;IASxB,OAAO,CAAC,IAAI;IAIZ,OAAO,CAAC,aAAa;CAetB"}
|