@iobroker/dm-utils 1.0.16 → 3.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.
@@ -1,37 +1,40 @@
1
1
  import type { AdapterInstance } from '@iobroker/adapter-core';
2
2
  import type { ActionContext } from './ActionContext';
3
3
  import type { ProgressDialog } from './ProgressDialog';
4
- import { type DeviceDetails, type DeviceInfo, type ErrorResponse, type InstanceDetails, type JsonFormData, type JsonFormSchema, type RefreshResponse, type RetVal, type ActionButton } from './types';
5
- import type { ControlState } from './types/base';
6
- export declare abstract class DeviceManagement<T extends AdapterInstance = AdapterInstance> {
7
- protected readonly adapter: T;
4
+ import { type ActionButton, type DeviceDetails, type DeviceId, type DeviceInfo, type DeviceStatus, type ErrorResponse, type InstanceDetails, type JsonFormData, type JsonFormSchema, type RetVal } from './types';
5
+ import type { BackendToGuiCommand, DeviceRefreshResponse, InstanceRefreshResponse } from './types/base';
6
+ import type { ProgressOptions } from './types/common';
7
+ export type DeviceLoadContext<TId extends DeviceId> = {
8
+ addDevice(device: DeviceInfo<TId>): void;
9
+ setTotalDevices(count: number): void;
10
+ };
11
+ export declare abstract class DeviceManagement<TAdapter extends AdapterInstance = AdapterInstance, TId extends DeviceId = string> {
12
+ protected readonly adapter: TAdapter;
8
13
  private instanceInfo?;
9
14
  private devices?;
10
- private readonly contexts;
11
- constructor(adapter: T);
15
+ private readonly communicationStateId;
16
+ private readonly deviceLoadContexts;
17
+ private readonly messageContexts;
18
+ constructor(adapter: TAdapter, communicationStateId?: string | boolean);
19
+ private ensureCommunicationState;
20
+ protected sendCommandToGui(command: BackendToGuiCommand<TId>): Promise<void>;
12
21
  protected get log(): ioBroker.Log;
13
22
  protected getInstanceInfo(): RetVal<InstanceDetails>;
14
- protected abstract listDevices(): RetVal<DeviceInfo[]>;
15
- protected getDeviceDetails(id: string): RetVal<DeviceDetails | null | {
23
+ protected abstract loadDevices(context: DeviceLoadContext<TId>): RetVal<void>;
24
+ protected getDeviceInfo(_deviceId: TId): RetVal<DeviceInfo<TId>>;
25
+ protected getDeviceStatus(_deviceId: TId): RetVal<DeviceStatus | DeviceStatus[]>;
26
+ protected getDeviceDetails(id: TId): RetVal<DeviceDetails<TId> | null | {
16
27
  error: string;
17
28
  }>;
18
- protected handleInstanceAction(actionId: string, context?: ActionContext, options?: {
19
- value?: number | string | boolean;
20
- [key: string]: any;
21
- }): RetVal<ErrorResponse> | RetVal<RefreshResponse>;
22
- protected handleDeviceAction(deviceId: string, actionId: string, context?: ActionContext, options?: {
23
- value?: number | string | boolean;
24
- [key: string]: any;
25
- }): RetVal<ErrorResponse> | RetVal<RefreshResponse>;
26
- protected handleDeviceControl(deviceId: string, controlId: string, newState: ControlState, context?: MessageContext): RetVal<ErrorResponse | ioBroker.State>;
27
- protected handleDeviceControlState(deviceId: string, controlId: string, context?: MessageContext): RetVal<ErrorResponse | ioBroker.State>;
29
+ private handleInstanceAction;
30
+ private handleDeviceAction;
31
+ private handleDeviceControl;
32
+ private handleDeviceControlState;
28
33
  private onMessage;
29
34
  private handleMessage;
30
- private convertActions;
31
- private convertControls;
32
35
  private sendReply;
33
36
  }
34
- export declare class MessageContext implements ActionContext {
37
+ export declare class MessageContext<TId extends DeviceId> implements ActionContext {
35
38
  private readonly adapter;
36
39
  private hasOpenProgressDialog;
37
40
  private lastMessage?;
@@ -44,13 +47,9 @@ export declare class MessageContext implements ActionContext {
44
47
  title?: ioBroker.StringOrTranslated;
45
48
  buttons?: (ActionButton | 'apply' | 'cancel' | 'close')[];
46
49
  }): Promise<JsonFormData | undefined>;
47
- openProgress(title: string, options?: {
48
- indeterminate?: boolean;
49
- value?: number;
50
- label?: string;
51
- }): Promise<ProgressDialog>;
52
- sendFinalResult(result: ErrorResponse | RefreshResponse): void;
53
- sendControlResult(deviceId: string, controlId: string, result: ErrorResponse | ioBroker.State): void;
50
+ openProgress(title: ioBroker.StringOrTranslated, options?: ProgressOptions): Promise<ProgressDialog>;
51
+ sendFinalResult(result: ErrorResponse | DeviceRefreshResponse<'api', TId> | InstanceRefreshResponse): void;
52
+ sendControlResult(deviceId: TId, controlId: string, result: ErrorResponse | ioBroker.State): void;
54
53
  handleProgress(message: ioBroker.Message): void;
55
54
  private checkPreconditions;
56
55
  private send;
@@ -3,16 +3,62 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MessageContext = exports.DeviceManagement = void 0;
4
4
  const types_1 = require("./types");
5
5
  class DeviceManagement {
6
- constructor(adapter) {
6
+ constructor(adapter, communicationStateId) {
7
7
  this.adapter = adapter;
8
- this.contexts = new Map();
8
+ this.deviceLoadContexts = new Map();
9
+ this.messageContexts = new Map();
9
10
  adapter.on('message', this.onMessage.bind(this));
11
+ if (communicationStateId === true) {
12
+ // use standard ID `info.deviceManager`
13
+ this.communicationStateId = 'info.deviceManager';
14
+ }
15
+ else if (communicationStateId) {
16
+ this.communicationStateId = communicationStateId;
17
+ }
18
+ if (this.communicationStateId) {
19
+ this.ensureCommunicationState().catch(e => this.log().error(`Cannot initialize communication state: ${e}`));
20
+ }
21
+ }
22
+ async ensureCommunicationState() {
23
+ let stateObj = await this.adapter.getObjectAsync(this.communicationStateId);
24
+ if (!stateObj) {
25
+ stateObj = {
26
+ _id: this.communicationStateId,
27
+ type: 'state',
28
+ common: {
29
+ expert: true,
30
+ name: 'Communication with GUI for device manager',
31
+ type: 'string',
32
+ role: 'state',
33
+ def: '',
34
+ read: true,
35
+ write: false,
36
+ },
37
+ native: {},
38
+ };
39
+ await this.adapter.setObjectAsync(this.communicationStateId, stateObj);
40
+ }
41
+ }
42
+ async sendCommandToGui(command) {
43
+ if (this.communicationStateId) {
44
+ await this.adapter.setState(this.communicationStateId, JSON.stringify(command), true);
45
+ }
46
+ else {
47
+ throw new Error('Communication state not found');
48
+ }
10
49
  }
11
50
  get log() {
12
51
  return this.adapter.log;
13
52
  }
14
53
  getInstanceInfo() {
15
- return { apiVersion: 'v1' };
54
+ // Overload this method if your adapter does not use BackendToGui communication and States/Objects in DeviceInfo
55
+ return { apiVersion: 'v3', communicationStateId: this.communicationStateId || undefined };
56
+ }
57
+ getDeviceInfo(_deviceId) {
58
+ throw new Error('Do not send "infoUpdate" or "delete" command without implementing getDeviceInfo method!');
59
+ }
60
+ getDeviceStatus(_deviceId) {
61
+ throw new Error('Do not send "statusUpdate" command without implementing getDeviceStatus method!');
16
62
  }
17
63
  getDeviceDetails(id) {
18
64
  return { id, schema: {} };
@@ -38,7 +84,7 @@ class DeviceManagement {
38
84
  },
39
85
  };
40
86
  }
41
- if (!action.handler) {
87
+ if (!('handler' in action) || !action.handler) {
42
88
  this.log.warn(`Instance action ${actionId} is disabled because it has no handler`);
43
89
  return {
44
90
  error: {
@@ -52,40 +98,41 @@ class DeviceManagement {
52
98
  handleDeviceAction(deviceId, actionId, context, options) {
53
99
  var _a;
54
100
  if (!this.devices) {
55
- this.log.warn(`Device action ${actionId} was called before listDevices()`);
101
+ this.log.warn(`Device action ${actionId} was called before loadDevices()`);
56
102
  return {
57
103
  error: {
58
104
  code: types_1.ErrorCodes.E_DEVICE_ACTION_NOT_INITIALIZED,
59
- message: `Device action ${actionId} was called before listDevices()`,
105
+ message: `Device action ${actionId} was called before loadDevices()`,
60
106
  },
61
107
  };
62
108
  }
63
- const device = this.devices.get(deviceId);
109
+ const jsonId = JSON.stringify(deviceId);
110
+ const device = this.devices.get(jsonId);
64
111
  if (!device) {
65
- this.log.warn(`Device action ${actionId} was called on unknown device: ${deviceId}`);
112
+ this.log.warn(`Device action ${actionId} was called on unknown device: ${jsonId}`);
66
113
  return {
67
114
  error: {
68
115
  code: types_1.ErrorCodes.E_DEVICE_ACTION_DEVICE_UNKNOWN,
69
- message: `Device action ${actionId} was called on unknown device: ${deviceId}`,
116
+ message: `Device action ${actionId} was called on unknown device: ${jsonId}`,
70
117
  },
71
118
  };
72
119
  }
73
120
  const action = (_a = device.actions) === null || _a === void 0 ? void 0 : _a.find(a => a.id === actionId);
74
121
  if (!action) {
75
- this.log.warn(`Device action ${actionId} doesn't exist on device ${deviceId}`);
122
+ this.log.warn(`Device action ${actionId} doesn't exist on device ${jsonId}`);
76
123
  return {
77
124
  error: {
78
125
  code: types_1.ErrorCodes.E_DEVICE_ACTION_UNKNOWN,
79
- message: `Device action ${actionId} doesn't exist on device ${deviceId}`,
126
+ message: `Device action ${actionId} doesn't exist on device ${jsonId}`,
80
127
  },
81
128
  };
82
129
  }
83
- if (!action.handler) {
84
- this.log.warn(`Device action ${actionId} on ${deviceId} is disabled because it has no handler`);
130
+ if (!('handler' in action) || !action.handler) {
131
+ this.log.warn(`Device action ${actionId} on ${jsonId} is disabled because it has no handler`);
85
132
  return {
86
133
  error: {
87
134
  code: types_1.ErrorCodes.E_DEVICE_ACTION_NO_HANDLER,
88
- message: `Device action ${actionId} on ${deviceId} is disabled because it has no handler`,
135
+ message: `Device action ${actionId} on ${jsonId} is disabled because it has no handler`,
89
136
  },
90
137
  };
91
138
  }
@@ -94,40 +141,41 @@ class DeviceManagement {
94
141
  handleDeviceControl(deviceId, controlId, newState, context) {
95
142
  var _a;
96
143
  if (!this.devices) {
97
- this.log.warn(`Device control ${controlId} was called before listDevices()`);
144
+ this.log.warn(`Device control ${controlId} was called before loadDevices()`);
98
145
  return {
99
146
  error: {
100
147
  code: types_1.ErrorCodes.E_DEVICE_CONTROL_NOT_INITIALIZED,
101
- message: `Device control ${controlId} was called before listDevices()`,
148
+ message: `Device control ${controlId} was called before loadDevices()`,
102
149
  },
103
150
  };
104
151
  }
105
- const device = this.devices.get(deviceId);
152
+ const jsonId = JSON.stringify(deviceId);
153
+ const device = this.devices.get(jsonId);
106
154
  if (!device) {
107
- this.log.warn(`Device control ${controlId} was called on unknown device: ${deviceId}`);
155
+ this.log.warn(`Device control ${controlId} was called on unknown device: ${jsonId}`);
108
156
  return {
109
157
  error: {
110
158
  code: types_1.ErrorCodes.E_DEVICE_CONTROL_DEVICE_UNKNOWN,
111
- message: `Device control ${controlId} was called on unknown device: ${deviceId}`,
159
+ message: `Device control ${controlId} was called on unknown device: ${jsonId}`,
112
160
  },
113
161
  };
114
162
  }
115
163
  const control = (_a = device.controls) === null || _a === void 0 ? void 0 : _a.find(a => a.id === controlId);
116
164
  if (!control) {
117
- this.log.warn(`Device control ${controlId} doesn't exist on device ${deviceId}`);
165
+ this.log.warn(`Device control ${controlId} doesn't exist on device ${jsonId}`);
118
166
  return {
119
167
  error: {
120
168
  code: types_1.ErrorCodes.E_DEVICE_CONTROL_UNKNOWN,
121
- message: `Device control ${controlId} doesn't exist on device ${deviceId}`,
169
+ message: `Device control ${controlId} doesn't exist on device ${jsonId}`,
122
170
  },
123
171
  };
124
172
  }
125
173
  if (!control.handler) {
126
- this.log.warn(`Device control ${controlId} on ${deviceId} is disabled because it has no handler`);
174
+ this.log.warn(`Device control ${controlId} on ${jsonId} is disabled because it has no handler`);
127
175
  return {
128
176
  error: {
129
177
  code: types_1.ErrorCodes.E_DEVICE_CONTROL_NO_HANDLER,
130
- message: `Device control ${controlId} on ${deviceId} is disabled because it has no handler`,
178
+ message: `Device control ${controlId} on ${jsonId} is disabled because it has no handler`,
131
179
  },
132
180
  };
133
181
  }
@@ -137,40 +185,41 @@ class DeviceManagement {
137
185
  handleDeviceControlState(deviceId, controlId, context) {
138
186
  var _a;
139
187
  if (!this.devices) {
140
- this.log.warn(`Device get state ${controlId} was called before listDevices()`);
188
+ this.log.warn(`Device get state ${controlId} was called before loadDevices()`);
141
189
  return {
142
190
  error: {
143
191
  code: types_1.ErrorCodes.E_DEVICE_GET_STATE_NOT_INITIALIZED,
144
- message: `Device control ${controlId} was called before listDevices()`,
192
+ message: `Device control ${controlId} was called before loadDevices()`,
145
193
  },
146
194
  };
147
195
  }
148
- const device = this.devices.get(deviceId);
196
+ const jsonId = JSON.stringify(deviceId);
197
+ const device = this.devices.get(jsonId);
149
198
  if (!device) {
150
- this.log.warn(`Device get state ${controlId} was called on unknown device: ${deviceId}`);
199
+ this.log.warn(`Device get state ${controlId} was called on unknown device: ${jsonId}`);
151
200
  return {
152
201
  error: {
153
202
  code: types_1.ErrorCodes.E_DEVICE_GET_STATE_DEVICE_UNKNOWN,
154
- message: `Device control ${controlId} was called on unknown device: ${deviceId}`,
203
+ message: `Device control ${controlId} was called on unknown device: ${jsonId}`,
155
204
  },
156
205
  };
157
206
  }
158
207
  const control = (_a = device.controls) === null || _a === void 0 ? void 0 : _a.find(a => a.id === controlId);
159
208
  if (!control) {
160
- this.log.warn(`Device get state ${controlId} doesn't exist on device ${deviceId}`);
209
+ this.log.warn(`Device get state ${controlId} doesn't exist on device ${jsonId}`);
161
210
  return {
162
211
  error: {
163
212
  code: types_1.ErrorCodes.E_DEVICE_GET_STATE_UNKNOWN,
164
- message: `Device control ${controlId} doesn't exist on device ${deviceId}`,
213
+ message: `Device control ${controlId} doesn't exist on device ${jsonId}`,
165
214
  },
166
215
  };
167
216
  }
168
217
  if (!control.getStateHandler) {
169
- this.log.warn(`Device get state ${controlId} on ${deviceId} is disabled because it has no handler`);
218
+ this.log.warn(`Device get state ${controlId} on ${jsonId} is disabled because it has no handler`);
170
219
  return {
171
220
  error: {
172
221
  code: types_1.ErrorCodes.E_DEVICE_GET_STATE_NO_HANDLER,
173
- message: `Device get state ${controlId} on ${deviceId} is disabled because it has no handler`,
222
+ message: `Device get state ${controlId} on ${jsonId} is disabled because it has no handler`,
174
223
  },
175
224
  };
176
225
  }
@@ -183,73 +232,110 @@ class DeviceManagement {
183
232
  void this.handleMessage(obj).catch(this.log.error);
184
233
  }
185
234
  async handleMessage(msg) {
235
+ var _a;
186
236
  this.log.debug(`DeviceManagement received: ${JSON.stringify(msg)}`);
187
237
  switch (msg.command) {
188
238
  case 'dm:instanceInfo': {
189
239
  this.instanceInfo = await this.getInstanceInfo();
190
- this.sendReply(Object.assign(Object.assign({}, this.instanceInfo), { actions: this.convertActions(this.instanceInfo.actions) }), msg);
240
+ this.sendReply(Object.assign(Object.assign({}, this.instanceInfo), { actions: convertActions(this.instanceInfo.actions) }), msg);
191
241
  return;
192
242
  }
193
- case 'dm:listDevices': {
194
- const deviceList = await this.listDevices();
195
- this.devices = deviceList.reduce((map, value) => {
196
- if (map.has(value.id)) {
197
- throw new Error(`Device ID ${value.id} is not unique`);
243
+ case 'dm:loadDevices': {
244
+ const context = new DeviceLoadContextImpl(msg, this.adapter);
245
+ this.deviceLoadContexts.set(msg._id, context);
246
+ await this.loadDevices(context);
247
+ if (context.complete()) {
248
+ this.deviceLoadContexts.delete(msg._id);
249
+ }
250
+ this.devices = context.devices.reduce((map, value) => {
251
+ const jsonId = JSON.stringify(value.id);
252
+ if (map.has(jsonId)) {
253
+ throw new Error(`Device ID ${jsonId} is not unique`);
198
254
  }
199
- map.set(value.id, value);
255
+ map.set(jsonId, value);
200
256
  return map;
201
257
  }, new Map());
202
- const apiDeviceList = deviceList.map(d => (Object.assign(Object.assign({}, d), { actions: this.convertActions(d.actions), controls: this.convertControls(d.controls) })));
203
- this.sendReply(apiDeviceList, msg);
204
- this.adapter.sendTo(msg.from, msg.command, this.devices, msg.callback);
258
+ return;
259
+ }
260
+ case 'dm:deviceInfo': {
261
+ const deviceInfo = await this.getDeviceInfo(msg.message);
262
+ this.sendReply(Object.assign(Object.assign({}, deviceInfo), { actions: convertActions(deviceInfo.actions), controls: convertControls(deviceInfo.controls) }), msg);
263
+ return;
264
+ }
265
+ case 'dm:deviceStatus': {
266
+ const deviceStatus = await this.getDeviceStatus(msg.message);
267
+ this.sendReply(deviceStatus, msg);
205
268
  return;
206
269
  }
207
270
  case 'dm:deviceDetails': {
208
271
  const details = await this.getDeviceDetails(msg.message);
209
- this.adapter.sendTo(msg.from, msg.command, details, msg.callback);
272
+ this.sendReply(details, msg);
210
273
  return;
211
274
  }
212
275
  case 'dm:instanceAction': {
213
276
  const action = msg.message;
214
277
  const context = new MessageContext(msg, this.adapter);
215
- this.contexts.set(msg._id, context);
278
+ this.messageContexts.set(msg._id, context);
216
279
  const result = await this.handleInstanceAction(action.actionId, context, { value: action.value });
217
- this.contexts.delete(msg._id);
280
+ this.messageContexts.delete(msg._id);
218
281
  context.sendFinalResult(result);
219
282
  return;
220
283
  }
221
284
  case 'dm:deviceAction': {
222
285
  const action = msg.message;
223
286
  const context = new MessageContext(msg, this.adapter);
224
- this.contexts.set(msg._id, context);
287
+ this.messageContexts.set(msg._id, context);
225
288
  const result = await this.handleDeviceAction(action.deviceId, action.actionId, context, {
226
289
  value: action.value,
227
290
  });
228
- this.contexts.delete(msg._id);
229
- context.sendFinalResult(result);
291
+ this.messageContexts.delete(msg._id);
292
+ if ('update' in result) {
293
+ // special handling for update responses (we need to update our cache and convert actions/controls before sending to GUI)
294
+ const update = result.update;
295
+ (_a = this.devices) === null || _a === void 0 ? void 0 : _a.set(JSON.stringify(update.id), update);
296
+ context.sendFinalResult({
297
+ update: Object.assign(Object.assign({}, update), { actions: convertActions(update.actions), controls: convertControls(update.controls) }),
298
+ });
299
+ }
300
+ else {
301
+ context.sendFinalResult(result);
302
+ }
230
303
  return;
231
304
  }
232
305
  case 'dm:deviceControl': {
233
306
  const control = msg.message;
234
307
  const context = new MessageContext(msg, this.adapter);
235
- this.contexts.set(msg._id, context);
308
+ this.messageContexts.set(msg._id, context);
236
309
  const result = await this.handleDeviceControl(control.deviceId, control.controlId, control.state, context);
237
- this.contexts.delete(msg._id);
310
+ this.messageContexts.delete(msg._id);
238
311
  context.sendControlResult(control.deviceId, control.controlId, result);
239
312
  return;
240
313
  }
241
314
  case 'dm:deviceControlState': {
242
315
  const control = msg.message;
243
316
  const context = new MessageContext(msg, this.adapter);
244
- this.contexts.set(msg._id, context);
317
+ this.messageContexts.set(msg._id, context);
245
318
  const result = await this.handleDeviceControlState(control.deviceId, control.controlId, context);
246
- this.contexts.delete(msg._id);
319
+ this.messageContexts.delete(msg._id);
247
320
  context.sendControlResult(control.deviceId, control.controlId, result);
248
321
  return;
249
322
  }
323
+ case 'dm:deviceLoadProgress': {
324
+ const { origin } = msg.message;
325
+ const context = this.deviceLoadContexts.get(origin);
326
+ if (!context) {
327
+ this.log.warn(`Unknown message origin: ${origin}`);
328
+ this.sendReply({ error: 'Unknown load progress origin' }, msg);
329
+ return;
330
+ }
331
+ if (context.handleProgress(msg)) {
332
+ this.deviceLoadContexts.delete(origin);
333
+ }
334
+ return;
335
+ }
250
336
  case 'dm:actionProgress': {
251
337
  const { origin } = msg.message;
252
- const context = this.contexts.get(origin);
338
+ const context = this.messageContexts.get(origin);
253
339
  if (!context) {
254
340
  this.log.warn(`Unknown message origin: ${origin}`);
255
341
  this.sendReply({ error: 'Unknown action origin' }, msg);
@@ -260,41 +346,57 @@ class DeviceManagement {
260
346
  }
261
347
  }
262
348
  }
263
- convertActions(actions) {
264
- if (!actions) {
265
- return undefined;
266
- }
267
- // detect duplicate IDs
268
- const ids = new Set();
269
- actions.forEach(a => {
270
- if (ids.has(a.id)) {
271
- throw new Error(`Action ID ${a.id} is used twice, this would lead to unexpected behavior`);
272
- }
273
- ids.add(a.id);
274
- });
275
- // remove handler function to send it as JSON
276
- return actions.map((a) => (Object.assign(Object.assign({}, a), { handler: undefined, disabled: !a.handler })));
277
- }
278
- convertControls(controls) {
279
- if (!controls) {
280
- return undefined;
281
- }
282
- // detect duplicate IDs
283
- const ids = new Set();
284
- controls.forEach(a => {
285
- if (ids.has(a.id)) {
286
- throw new Error(`Control ID ${a.id} is used twice, this would lead to unexpected behavior`);
287
- }
288
- ids.add(a.id);
289
- });
290
- // remove handler function to send it as JSON
291
- return controls.map((a) => (Object.assign(Object.assign({}, a), { handler: undefined, getStateHandler: undefined })));
292
- }
293
349
  sendReply(reply, msg) {
294
350
  this.adapter.sendTo(msg.from, msg.command, reply, msg.callback);
295
351
  }
296
352
  }
297
353
  exports.DeviceManagement = DeviceManagement;
354
+ class DeviceLoadContextImpl {
355
+ constructor(msg, adapter) {
356
+ this.adapter = adapter;
357
+ this.minBatchSize = 8;
358
+ this.devices = [];
359
+ this.sendNext = [];
360
+ this.completed = false;
361
+ this.respondTo = msg;
362
+ this.id = msg._id;
363
+ }
364
+ addDevice(device) {
365
+ this.devices.push(device);
366
+ this.sendNext.push(Object.assign(Object.assign({}, device), { actions: convertActions(device.actions), controls: convertControls(device.controls) }));
367
+ this.flush();
368
+ }
369
+ setTotalDevices(count) {
370
+ this.totalDevices = count;
371
+ this.flush();
372
+ }
373
+ complete() {
374
+ this.completed = true;
375
+ return this.flush();
376
+ }
377
+ handleProgress(message) {
378
+ this.respondTo = message;
379
+ return this.flush();
380
+ }
381
+ flush() {
382
+ if (this.sendNext.length <= this.minBatchSize && !this.completed) {
383
+ return false;
384
+ }
385
+ if (!this.respondTo) {
386
+ return false;
387
+ }
388
+ const reply = {
389
+ add: this.sendNext,
390
+ total: this.totalDevices,
391
+ next: this.completed ? undefined : { origin: this.id },
392
+ };
393
+ this.sendNext = [];
394
+ const msg = this.respondTo;
395
+ this.respondTo = undefined;
396
+ this.adapter.sendTo(msg.from, msg.command, reply, msg.callback);
397
+ return this.completed;
398
+ }
399
+ }
298
400
  class MessageContext {
299
401
  constructor(msg, adapter) {
300
402
  this.adapter = adapter;
@@ -306,9 +408,7 @@ class MessageContext {
306
408
  const promise = new Promise(resolve => {
307
409
  this.progressHandler = () => resolve();
308
410
  });
309
- this.send('message', {
310
- message: text,
311
- });
411
+ this.send({ type: 'message', message: text });
312
412
  return promise;
313
413
  }
314
414
  showConfirmation(text) {
@@ -316,9 +416,7 @@ class MessageContext {
316
416
  const promise = new Promise(resolve => {
317
417
  this.progressHandler = msg => resolve(!!msg.confirm);
318
418
  });
319
- this.send('confirm', {
320
- confirm: text,
321
- });
419
+ this.send({ type: 'confirm', confirm: text });
322
420
  return promise;
323
421
  }
324
422
  showForm(schema, options) {
@@ -326,7 +424,8 @@ class MessageContext {
326
424
  const promise = new Promise(resolve => {
327
425
  this.progressHandler = msg => resolve(msg.data);
328
426
  });
329
- this.send('form', {
427
+ this.send({
428
+ type: 'form',
330
429
  form: Object.assign({ schema }, options),
331
430
  });
332
431
  return promise;
@@ -339,9 +438,7 @@ class MessageContext {
339
438
  const promise = new Promise(resolve => {
340
439
  this.progressHandler = () => resolve();
341
440
  });
342
- this.send('progress', {
343
- progress: Object.assign(Object.assign(Object.assign({ title }, options), update), { open: true }),
344
- }, true);
441
+ this.send({ type: 'progress', progress: update }, true);
345
442
  return promise;
346
443
  },
347
444
  close: () => {
@@ -351,28 +448,23 @@ class MessageContext {
351
448
  resolve();
352
449
  };
353
450
  });
354
- this.send('progress', {
355
- progress: { open: false },
356
- });
451
+ this.send({ type: 'progress', progress: { open: false } });
357
452
  return promise;
358
453
  },
359
454
  };
360
455
  const promise = new Promise(resolve => {
361
456
  this.progressHandler = () => resolve(dialog);
362
457
  });
363
- this.send('progress', {
364
- progress: Object.assign(Object.assign({ title }, options), { open: true }),
365
- }, true);
458
+ this.send({ type: 'progress', progress: Object.assign(Object.assign({ title }, options), { open: true }) }, true);
366
459
  return promise;
367
460
  }
368
461
  sendFinalResult(result) {
369
- this.send('result', {
370
- result,
371
- });
462
+ this.send({ type: 'result', result });
372
463
  }
373
464
  sendControlResult(deviceId, controlId, result) {
374
465
  if (typeof result === 'object' && 'error' in result) {
375
- this.send('result', {
466
+ this.send({
467
+ type: 'result',
376
468
  result: {
377
469
  error: result.error,
378
470
  deviceId,
@@ -381,7 +473,8 @@ class MessageContext {
381
473
  });
382
474
  }
383
475
  else {
384
- this.send('result', {
476
+ this.send({
477
+ type: 'result',
385
478
  result: {
386
479
  state: result,
387
480
  deviceId,
@@ -403,11 +496,11 @@ class MessageContext {
403
496
  throw new Error("Can't show another dialog while a progress dialog is open. Please call 'close()' on the dialog before opening another dialog.");
404
497
  }
405
498
  }
406
- send(type, message, doNotClose) {
499
+ send(message, doNotClose) {
407
500
  if (!this.lastMessage) {
408
501
  throw new Error("No outstanding message, can't send a new one");
409
502
  }
410
- this.adapter.sendTo(this.lastMessage.from, this.lastMessage.command, Object.assign(Object.assign({}, message), { type, origin: this.lastMessage.message.origin || this.lastMessage._id }), this.lastMessage.callback);
503
+ this.adapter.sendTo(this.lastMessage.from, this.lastMessage.command, Object.assign(Object.assign({}, message), { origin: this.lastMessage.message.origin || this.lastMessage._id }), this.lastMessage.callback);
411
504
  if (!doNotClose) {
412
505
  // "progress" is exception. It will be closed with "close" flag
413
506
  this.lastMessage = undefined;
@@ -415,3 +508,33 @@ class MessageContext {
415
508
  }
416
509
  }
417
510
  exports.MessageContext = MessageContext;
511
+ function convertActions(actions) {
512
+ if (!actions) {
513
+ return undefined;
514
+ }
515
+ // detect duplicate IDs
516
+ const ids = new Set();
517
+ actions.forEach(a => {
518
+ if (ids.has(a.id)) {
519
+ throw new Error(`Action ID ${a.id} is used twice, this would lead to unexpected behavior`);
520
+ }
521
+ ids.add(a.id);
522
+ });
523
+ // remove handler function to send it as JSON
524
+ return actions.map((a) => (Object.assign(Object.assign({}, a), { handler: undefined, disabled: !a.handler && !a.url })));
525
+ }
526
+ function convertControls(controls) {
527
+ if (!controls) {
528
+ return undefined;
529
+ }
530
+ // detect duplicate IDs
531
+ const ids = new Set();
532
+ controls.forEach(a => {
533
+ if (ids.has(a.id)) {
534
+ throw new Error(`Control ID ${a.id} is used twice, this would lead to unexpected behavior`);
535
+ }
536
+ ids.add(a.id);
537
+ });
538
+ // remove handler function to send it as JSON
539
+ return controls.map((a) => (Object.assign(Object.assign({}, a), { handler: undefined, getStateHandler: undefined })));
540
+ }
@@ -1,9 +1,5 @@
1
+ import type { ProgressUpdate } from './types/common';
1
2
  export interface ProgressDialog {
2
- update(update: {
3
- title?: ioBroker.StringOrTranslated;
4
- indeterminate?: boolean;
5
- value?: number;
6
- label?: ioBroker.StringOrTranslated;
7
- }): Promise<void>;
3
+ update(update: ProgressUpdate): Promise<void>;
8
4
  close(): Promise<void>;
9
5
  }