daikin-local-control 0.3.0 → 0.4.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/DESIGN.md +113 -2
- package/README.md +131 -13
- package/UNSUPPORTED.md +42 -4
- package/dist/clients/classic.d.ts +2 -2
- package/dist/clients/classic.d.ts.map +1 -1
- package/dist/clients/classic.js +4 -4
- package/dist/clients/classic.js.map +1 -1
- package/dist/clients/client.d.ts +6 -2
- package/dist/clients/client.d.ts.map +1 -1
- package/dist/clients/dsiot.d.ts +3 -1
- package/dist/clients/dsiot.d.ts.map +1 -1
- package/dist/clients/dsiot.js +11 -3
- package/dist/clients/dsiot.js.map +1 -1
- package/dist/codecs/classic/capabilities.d.ts.map +1 -1
- package/dist/codecs/classic/capabilities.js +5 -11
- package/dist/codecs/classic/capabilities.js.map +1 -1
- package/dist/codecs/classic/extendedControl.d.ts.map +1 -1
- package/dist/codecs/classic/extendedControl.js +10 -19
- package/dist/codecs/classic/extendedControl.js.map +1 -1
- package/dist/codecs/classic/state.d.ts.map +1 -1
- package/dist/codecs/classic/state.js +36 -95
- package/dist/codecs/classic/state.js.map +1 -1
- package/dist/codecs/classic/tables.d.ts +32 -0
- package/dist/codecs/classic/tables.d.ts.map +1 -0
- package/dist/codecs/classic/tables.js +94 -0
- package/dist/codecs/classic/tables.js.map +1 -0
- package/dist/codecs/credentials.d.ts +3 -2
- package/dist/codecs/credentials.d.ts.map +1 -1
- package/dist/codecs/credentials.js +8 -3
- package/dist/codecs/credentials.js.map +1 -1
- package/dist/codecs/device.d.ts +2 -1
- package/dist/codecs/device.d.ts.map +1 -1
- package/dist/codecs/device.js +7 -6
- package/dist/codecs/device.js.map +1 -1
- package/dist/codecs/dsiot/capabilities.d.ts.map +1 -1
- package/dist/codecs/dsiot/capabilities.js +8 -15
- package/dist/codecs/dsiot/capabilities.js.map +1 -1
- package/dist/codecs/dsiot/device.d.ts +4 -2
- package/dist/codecs/dsiot/device.d.ts.map +1 -1
- package/dist/codecs/dsiot/device.js +8 -8
- package/dist/codecs/dsiot/device.js.map +1 -1
- package/dist/codecs/dsiot/multireq.d.ts +0 -18
- package/dist/codecs/dsiot/multireq.d.ts.map +1 -1
- package/dist/codecs/dsiot/multireq.js +2 -24
- package/dist/codecs/dsiot/multireq.js.map +1 -1
- package/dist/codecs/dsiot/state.d.ts +6 -62
- package/dist/codecs/dsiot/state.d.ts.map +1 -1
- package/dist/codecs/dsiot/state.js +29 -148
- package/dist/codecs/dsiot/state.js.map +1 -1
- package/dist/codecs/dsiot/tables.d.ts +116 -0
- package/dist/codecs/dsiot/tables.d.ts.map +1 -0
- package/dist/codecs/dsiot/tables.js +185 -0
- package/dist/codecs/dsiot/tables.js.map +1 -0
- package/dist/discovery.d.ts +2 -1
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +9 -5
- package/dist/discovery.js.map +1 -1
- package/dist/factory.d.ts +7 -4
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +4 -2
- package/dist/factory.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/models/device.d.ts +3 -1
- package/dist/models/device.d.ts.map +1 -1
- package/dist/models/state.d.ts +2 -3
- package/dist/models/state.d.ts.map +1 -1
- package/dist/simulators/classic.d.ts +61 -0
- package/dist/simulators/classic.d.ts.map +1 -0
- package/dist/simulators/classic.js +473 -0
- package/dist/simulators/classic.js.map +1 -0
- package/dist/simulators/dsiot.d.ts +49 -0
- package/dist/simulators/dsiot.d.ts.map +1 -0
- package/dist/simulators/dsiot.js +519 -0
- package/dist/simulators/dsiot.js.map +1 -0
- package/dist/simulators/index.d.ts +4 -0
- package/dist/simulators/index.d.ts.map +1 -0
- package/dist/simulators/index.js +3 -0
- package/dist/simulators/index.js.map +1 -0
- package/dist/simulators/simulator.d.ts +116 -0
- package/dist/simulators/simulator.d.ts.map +1 -0
- package/dist/simulators/simulator.js +141 -0
- package/dist/simulators/simulator.js.map +1 -0
- package/dist/transport.d.ts +6 -2
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +7 -2
- package/dist/transport.js.map +1 -1
- package/package.json +5 -2
- package/src/clients/classic.ts +8 -8
- package/src/clients/client.ts +7 -1
- package/src/clients/dsiot.ts +12 -2
- package/src/codecs/classic/capabilities.ts +8 -11
- package/src/codecs/classic/extendedControl.ts +14 -25
- package/src/codecs/classic/state.ts +49 -101
- package/src/codecs/classic/tables.ts +123 -0
- package/src/codecs/credentials.ts +11 -3
- package/src/codecs/device.ts +8 -7
- package/src/codecs/dsiot/capabilities.ts +10 -17
- package/src/codecs/dsiot/device.ts +8 -9
- package/src/codecs/dsiot/multireq.ts +2 -24
- package/src/codecs/dsiot/state.ts +57 -193
- package/src/codecs/dsiot/tables.ts +249 -0
- package/src/discovery.ts +9 -5
- package/src/factory.ts +7 -4
- package/src/index.ts +2 -0
- package/src/models/device.ts +3 -1
- package/src/models/state.ts +2 -3
- package/src/simulators/classic.ts +635 -0
- package/src/simulators/dsiot.ts +736 -0
- package/src/simulators/index.ts +9 -0
- package/src/simulators/simulator.ts +281 -0
- package/src/transport.ts +10 -3
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { createSocket, type Socket } from "node:dgram";
|
|
2
|
+
import { once } from "node:events";
|
|
3
|
+
import { createServer, type IncomingHttpHeaders } from "node:http";
|
|
4
|
+
import type { AddressInfo } from "node:net";
|
|
5
|
+
import type { DaikinDeviceInfo } from "../models/device.js";
|
|
6
|
+
import type {
|
|
7
|
+
DaikinSensors,
|
|
8
|
+
DaikinState,
|
|
9
|
+
SpecialMode,
|
|
10
|
+
} from "../models/state.js";
|
|
11
|
+
|
|
12
|
+
// What the classic and the DSIOT simulator share: the HTTP server, and the
|
|
13
|
+
// reply to a discovery probe, which both adapters send in the same form.
|
|
14
|
+
|
|
15
|
+
const DISCOVERY_PROBE = "DAIKIN_UDP/common/basic_info";
|
|
16
|
+
const DISCOVERY_PORT = 30050;
|
|
17
|
+
|
|
18
|
+
const DEFAULT_ADDRESS = "127.0.0.1";
|
|
19
|
+
|
|
20
|
+
/** One HTTP request a simulator received. */
|
|
21
|
+
export interface DaikinSimulatorRequest {
|
|
22
|
+
method: string;
|
|
23
|
+
/** From the leading `/`, without the query string. */
|
|
24
|
+
path: string;
|
|
25
|
+
/** Percent-decoded. */
|
|
26
|
+
query: Record<string, string>;
|
|
27
|
+
headers: IncomingHttpHeaders;
|
|
28
|
+
body: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** What `startClassicSimulator` and `startDsiotSimulator` both resolve with. */
|
|
32
|
+
export interface DaikinSimulator {
|
|
33
|
+
/** `<address>:<port>`, as `DaikinDevice.host` takes it: `127.0.0.1`, unless
|
|
34
|
+
* the simulator was started at another `address`, and a port that was free
|
|
35
|
+
* when it started. */
|
|
36
|
+
readonly host: string;
|
|
37
|
+
/** Every request received, oldest first, the ones the simulator refused
|
|
38
|
+
* included. A request is added once its whole body has arrived, before it
|
|
39
|
+
* waits for its turn to be answered. */
|
|
40
|
+
readonly requests: DaikinSimulatorRequest[];
|
|
41
|
+
/** Changes the unit as its remote control would. A field left out, or
|
|
42
|
+
* `null`, stays as the unit holds it, and a new `mode` takes up what the
|
|
43
|
+
* unit remembers for that mode before the other fields are applied. It
|
|
44
|
+
* throws an `Error` for a field the unit has nothing to hold, such as a
|
|
45
|
+
* `verticalPosition` on a unit without fixed louver positions, and holds
|
|
46
|
+
* any other value as given, whether or not `getCapabilities` lists it. */
|
|
47
|
+
setState(state: Partial<DaikinState>): void;
|
|
48
|
+
/** Changes what the unit's sensors read. A reading left out stays as it is,
|
|
49
|
+
* and `null` is a reading the unit does not report. */
|
|
50
|
+
setSensors(sensors: Partial<DaikinSensors>): void;
|
|
51
|
+
/** Closes every connection and stops listening. A request still waiting is
|
|
52
|
+
* never answered. */
|
|
53
|
+
stop(): Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** In °C. */
|
|
57
|
+
export interface DaikinSimulatorTemperatures {
|
|
58
|
+
min: number;
|
|
59
|
+
max: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** What a simulated unit has, where both protocols can say it. Anything else
|
|
63
|
+
* about a unit is set in the adapter's own terms: `modelInfo` on a classic
|
|
64
|
+
* simulator, and `leaves` on a DSIOT one. */
|
|
65
|
+
export interface DaikinSimulatorUnit {
|
|
66
|
+
/** A unit that only cools offers dehumidify, cool and fan. Defaults to
|
|
67
|
+
* false: a heat pump, which offers auto and heat as well. */
|
|
68
|
+
coolingOnly?: boolean;
|
|
69
|
+
/** Whether the fan speed can be set. Defaults to true. */
|
|
70
|
+
fan?: boolean;
|
|
71
|
+
/** The axes the louvers swing on: `vertical` moves the air up and down
|
|
72
|
+
* alone, and `both` left and right as well. Defaults to `both`. */
|
|
73
|
+
swing?: "none" | "vertical" | "both";
|
|
74
|
+
/** The special modes the unit takes. Defaults to all three. */
|
|
75
|
+
specialModes?: readonly SpecialMode[];
|
|
76
|
+
/** The setpoints cool takes. A classic adapter reports whole degrees.
|
|
77
|
+
* Defaults to 18 to 32 on a DSIOT simulator, and to none reported on a
|
|
78
|
+
* classic one. */
|
|
79
|
+
coolTemperaturesInC?: DaikinSimulatorTemperatures;
|
|
80
|
+
/** The setpoints heat takes. A classic adapter reports whole degrees.
|
|
81
|
+
* Defaults to 10 to 30 on a DSIOT simulator, and to none reported on a
|
|
82
|
+
* classic one. */
|
|
83
|
+
heatTemperaturesInC?: DaikinSimulatorTemperatures;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The options `startClassicSimulator` and `startDsiotSimulator` both take.
|
|
87
|
+
* A simulator applies `deviceInfo`, `state` and `sensors` after the fields
|
|
88
|
+
* given in the adapter's own terms, so they replace a field given both
|
|
89
|
+
* ways. */
|
|
90
|
+
export interface StartSimulatorOptions {
|
|
91
|
+
/** The adapter's name, MAC address and firmware version, as
|
|
92
|
+
* `getDeviceInfo` reads them back. A field left out keeps its default.
|
|
93
|
+
* Defaults to a name of `Simulator`. */
|
|
94
|
+
deviceInfo?: DaikinDeviceInfo;
|
|
95
|
+
/** The state the unit starts in: see `DaikinSimulator.setState`. Defaults
|
|
96
|
+
* to off, in cool at 24 °C on the auto fan speed, with nothing swinging
|
|
97
|
+
* and no special mode on. */
|
|
98
|
+
state?: Partial<DaikinState>;
|
|
99
|
+
/** What the unit's sensors read at the start: see
|
|
100
|
+
* `DaikinSimulator.setSensors`. Defaults to 26 °C and 55 % indoors, and
|
|
101
|
+
* 30 °C outdoors. */
|
|
102
|
+
sensors?: Partial<DaikinSensors>;
|
|
103
|
+
/** How long, in milliseconds, the adapter takes to answer a request. It
|
|
104
|
+
* answers one request at a time, in the order they arrive, so a request
|
|
105
|
+
* waits this long for each one ahead of it as well. Defaults to 0. */
|
|
106
|
+
replyDelayInMs?: number;
|
|
107
|
+
/** The address of this machine the adapter is at: it listens for HTTP
|
|
108
|
+
* there, and answers a discovery probe from there. Defaults to `127.0.0.1`,
|
|
109
|
+
* which nothing outside the machine reaches. */
|
|
110
|
+
address?: string;
|
|
111
|
+
/** Whether the adapter answers the discovery probe, so `discoverDevices`
|
|
112
|
+
* finds it at `host`. Defaults to false. Any number of simulators answer
|
|
113
|
+
* at once, in one process or several, and `discoverDevices` finds each at
|
|
114
|
+
* its own port. Starting one rejects with the socket's error only while a
|
|
115
|
+
* program that is not a simulator holds UDP port 30050. */
|
|
116
|
+
answersDiscovery?: boolean;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** What `startSimulator` resolves with: the part of a `DaikinSimulator` that
|
|
120
|
+
* is the same for both protocols. */
|
|
121
|
+
export type SimulatorServer = Pick<
|
|
122
|
+
DaikinSimulator,
|
|
123
|
+
"host" | "requests" | "stop"
|
|
124
|
+
>;
|
|
125
|
+
|
|
126
|
+
export interface SimulatorReply {
|
|
127
|
+
statusCode: number;
|
|
128
|
+
body: string;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** The reply to a request whose answer from an adapter is not known: HTTP 501,
|
|
132
|
+
* which no adapter is known to send, so a caller cannot take it for one. */
|
|
133
|
+
export function unanswered(request: DaikinSimulatorRequest): SimulatorReply {
|
|
134
|
+
return {
|
|
135
|
+
statusCode: 501,
|
|
136
|
+
body: `The simulator does not answer ${request.method} ${request.path}`,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Joins fields as a classic reply and a discovery reply carry them:
|
|
141
|
+
* `key=value` pairs separated by commas, `ret` first. */
|
|
142
|
+
export function formatKeyValue(
|
|
143
|
+
ret: string,
|
|
144
|
+
fields: Record<string, string> = {},
|
|
145
|
+
): string {
|
|
146
|
+
return Object.entries({ ret, ...fields })
|
|
147
|
+
.map(([name, value]) => `${name}=${value}`)
|
|
148
|
+
.join(",");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Listens on a free port of `options.address` and answers each request with
|
|
152
|
+
* what `answer` returns for it, one request at a time. With
|
|
153
|
+
* `options.answersDiscovery`, it answers the discovery probe with
|
|
154
|
+
* `discoveryFields()` and the HTTP port, as `api_port`. */
|
|
155
|
+
export async function startSimulator(
|
|
156
|
+
options: StartSimulatorOptions,
|
|
157
|
+
answer: (request: DaikinSimulatorRequest) => SimulatorReply,
|
|
158
|
+
discoveryFields: () => Record<string, string>,
|
|
159
|
+
): Promise<SimulatorServer> {
|
|
160
|
+
const requests: DaikinSimulatorRequest[] = [];
|
|
161
|
+
let stopped = false;
|
|
162
|
+
let turn = Promise.resolve();
|
|
163
|
+
|
|
164
|
+
const server = createServer((incoming, response) => {
|
|
165
|
+
let body = "";
|
|
166
|
+
incoming.setEncoding("utf8");
|
|
167
|
+
incoming.on("data", (chunk: string) => {
|
|
168
|
+
body += chunk;
|
|
169
|
+
});
|
|
170
|
+
incoming.on("end", () => {
|
|
171
|
+
const url = new URL(incoming.url ?? "/", "http://simulator");
|
|
172
|
+
const request: DaikinSimulatorRequest = {
|
|
173
|
+
method: incoming.method ?? "",
|
|
174
|
+
path: url.pathname,
|
|
175
|
+
query: Object.fromEntries(url.searchParams),
|
|
176
|
+
headers: incoming.headers,
|
|
177
|
+
body,
|
|
178
|
+
};
|
|
179
|
+
requests.push(request);
|
|
180
|
+
turn = turn.then(async () => {
|
|
181
|
+
if (options.replyDelayInMs) {
|
|
182
|
+
await sleep(options.replyDelayInMs);
|
|
183
|
+
}
|
|
184
|
+
if (stopped) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const reply = answer(request);
|
|
188
|
+
response.statusCode = reply.statusCode;
|
|
189
|
+
response.end(reply.body);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
const address = options.address ?? DEFAULT_ADDRESS;
|
|
194
|
+
server.listen(0, address);
|
|
195
|
+
// `once` rejects when the server emits `error` first, as it does for an
|
|
196
|
+
// address the machine does not have.
|
|
197
|
+
await once(server, "listening");
|
|
198
|
+
const { port } = server.address() as AddressInfo;
|
|
199
|
+
|
|
200
|
+
const stopServer = async () => {
|
|
201
|
+
// A client keeps its connection open between requests, and `close` alone
|
|
202
|
+
// waits for it.
|
|
203
|
+
server.closeAllConnections();
|
|
204
|
+
server.close();
|
|
205
|
+
await once(server, "close");
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
let discoverySockets: Socket[] = [];
|
|
209
|
+
if (options.answersDiscovery) {
|
|
210
|
+
try {
|
|
211
|
+
discoverySockets = await startDiscoverySockets(address, () =>
|
|
212
|
+
formatKeyValue("OK", {
|
|
213
|
+
...discoveryFields(),
|
|
214
|
+
api_port: String(port),
|
|
215
|
+
}),
|
|
216
|
+
);
|
|
217
|
+
} catch (error) {
|
|
218
|
+
await stopServer();
|
|
219
|
+
throw error;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
host: `${address}:${port}`,
|
|
225
|
+
requests,
|
|
226
|
+
async stop() {
|
|
227
|
+
// A server closed a second time never emits `close`.
|
|
228
|
+
if (stopped) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
stopped = true;
|
|
232
|
+
await Promise.all(discoverySockets.map(closeSocket));
|
|
233
|
+
await stopServer();
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// The probe is a broadcast, which reaches a socket bound to every address and
|
|
239
|
+
// none bound to one. So each simulator hears it on a socket bound to every
|
|
240
|
+
// address, sharing the port with the other simulators, and answers from a
|
|
241
|
+
// second socket bound to its own address: the reply then comes from that
|
|
242
|
+
// address, and a probe sent to that address alone reaches that simulator
|
|
243
|
+
// alone. An adapter answers the address and port the probe came from.
|
|
244
|
+
async function startDiscoverySockets(
|
|
245
|
+
address: string,
|
|
246
|
+
reply: () => string,
|
|
247
|
+
): Promise<Socket[]> {
|
|
248
|
+
const own = createSocket({ type: "udp4", reuseAddr: true });
|
|
249
|
+
const every = createSocket({ type: "udp4", reuseAddr: true });
|
|
250
|
+
const sockets = [own, every];
|
|
251
|
+
for (const socket of sockets) {
|
|
252
|
+
socket.on("message", (message, remote) => {
|
|
253
|
+
if (message.toString("utf8") === DISCOVERY_PROBE) {
|
|
254
|
+
// Without a callback, a reply that fails to send is emitted as an
|
|
255
|
+
// `error` nothing listens for.
|
|
256
|
+
own.send(reply(), remote.port, remote.address, () => {});
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
try {
|
|
261
|
+
own.bind(DISCOVERY_PORT, address);
|
|
262
|
+
// `once` rejects when the socket emits `error` first, as it does for a
|
|
263
|
+
// port another program holds.
|
|
264
|
+
await once(own, "listening");
|
|
265
|
+
every.bind(DISCOVERY_PORT);
|
|
266
|
+
await once(every, "listening");
|
|
267
|
+
} catch (error) {
|
|
268
|
+
await Promise.all(sockets.map(closeSocket));
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
return sockets;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async function closeSocket(socket: Socket): Promise<void> {
|
|
275
|
+
socket.close();
|
|
276
|
+
await once(socket, "close");
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function sleep(delayInMs: number): Promise<void> {
|
|
280
|
+
return new Promise((resolve) => setTimeout(resolve, delayInMs));
|
|
281
|
+
}
|
package/src/transport.ts
CHANGED
|
@@ -71,7 +71,8 @@ export interface DaikinTransportOptions {
|
|
|
71
71
|
host: string;
|
|
72
72
|
/** HTTPS (a Marvell adapter in secure mode). */
|
|
73
73
|
usesHttps?: boolean;
|
|
74
|
-
/** The terminal uuid, sent as `X-Daikin-uuid` on every call
|
|
74
|
+
/** The terminal uuid, sent as `X-Daikin-uuid` on every call: 32 lowercase
|
|
75
|
+
* hexadecimal digits, a UUID without its dashes. */
|
|
75
76
|
terminalUuid: string;
|
|
76
77
|
authentication?: DaikinAuthentication;
|
|
77
78
|
/** How long, in milliseconds, to wait for each of the connection, the
|
|
@@ -102,9 +103,15 @@ export interface DaikinTlsOptions {
|
|
|
102
103
|
export class DaikinTransport {
|
|
103
104
|
private readonly origin: string;
|
|
104
105
|
private readonly client: Client;
|
|
106
|
+
private readonly terminalUuidHeaders: Record<string, string>;
|
|
105
107
|
|
|
106
|
-
/** Call `close` when done with the adapter.
|
|
108
|
+
/** Call `close` when done with the adapter. It throws an `Error` when
|
|
109
|
+
* `options.terminalUuid` is not in the form
|
|
110
|
+
* `DaikinTransportOptions.terminalUuid` describes. */
|
|
107
111
|
constructor(private readonly options: DaikinTransportOptions) {
|
|
112
|
+
// Before the Client, so a uuid that throws leaves no connection open.
|
|
113
|
+
this.terminalUuidHeaders = encodeTerminalUuid(options.terminalUuid);
|
|
114
|
+
|
|
108
115
|
const scheme = options.usesHttps ? "https" : "http";
|
|
109
116
|
this.origin = `${scheme}://${options.host.replace(/\/+$/, "")}`;
|
|
110
117
|
|
|
@@ -137,7 +144,7 @@ export class DaikinTransport {
|
|
|
137
144
|
.join("&");
|
|
138
145
|
|
|
139
146
|
const headers: Record<string, string> = {
|
|
140
|
-
...
|
|
147
|
+
...this.terminalUuidHeaders,
|
|
141
148
|
...((await this.options.authentication?.headers()) ?? {}),
|
|
142
149
|
...(request.jsonBody !== undefined ? JSON_CONTENT_HEADERS : {}),
|
|
143
150
|
};
|