document-drive 1.17.2 → 1.19.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.
@@ -6,20 +6,24 @@ import { OperationScope } from "document-model/document";
6
6
  import { logger } from "../../utils/logger";
7
7
  import { OperationError } from "../error";
8
8
  import {
9
- BaseListenerManager,
9
+ DefaultListenerManagerOptions,
10
+ DriveUpdateErrorHandler,
10
11
  ErrorStatus,
11
12
  GetStrandsOptions,
13
+ IBaseDocumentDriveServer,
14
+ IListenerManager,
12
15
  Listener,
16
+ ListenerManagerOptions,
13
17
  ListenerState,
14
18
  ListenerUpdate,
15
19
  OperationUpdate,
16
20
  StrandUpdate,
17
21
  SynchronizationUnit,
22
+ SynchronizationUnitQuery,
18
23
  } from "../types";
19
- import { PullResponderTransmitter } from "./transmitter";
20
- import { InternalTransmitter } from "./transmitter/internal";
21
- import { SwitchboardPushTransmitter } from "./transmitter/switchboard-push";
22
- import { ITransmitter, StrandUpdateSource } from "./transmitter/types";
24
+ import { StrandUpdateSource } from "./transmitter/types";
25
+
26
+ const ENABLE_SYNC_DEBUG = false;
23
27
 
24
28
  function debounce<T extends unknown[], R>(
25
29
  func: (...args: T) => Promise<R>,
@@ -45,30 +49,81 @@ function debounce<T extends unknown[], R>(
45
49
  });
46
50
  };
47
51
  }
48
- export class ListenerManager extends BaseListenerManager {
52
+
53
+ export class ListenerManager implements IListenerManager {
49
54
  static LISTENER_UPDATE_DELAY = 250;
55
+ private debugID = `[LM #${Math.floor(Math.random() * 999)}]`;
56
+ protected driveServer: IBaseDocumentDriveServer;
57
+ protected options: ListenerManagerOptions;
58
+
59
+ // driveId -> listenerId -> listenerState
60
+ protected listenerStateByDriveId = new Map<
61
+ string,
62
+ Map<string, ListenerState>
63
+ >();
64
+
65
+ constructor(
66
+ drive: IBaseDocumentDriveServer,
67
+ listenerState = new Map<string, Map<string, ListenerState>>(),
68
+ options: ListenerManagerOptions = DefaultListenerManagerOptions,
69
+ ) {
70
+ this.debugLog(`constructor(...)`);
71
+ this.driveServer = drive;
72
+ this.listenerStateByDriveId = listenerState;
73
+ this.options = { ...DefaultListenerManagerOptions, ...options };
74
+ }
50
75
 
51
- async getTransmitter(
52
- driveId: string,
53
- listenerId: string,
54
- ): Promise<ITransmitter | undefined> {
55
- return Promise.resolve(this.transmitters[driveId]?.[listenerId]);
76
+ private debugLog(...data: any[]) {
77
+ if (!ENABLE_SYNC_DEBUG) {
78
+ return;
79
+ }
80
+
81
+ if (data.length > 0 && typeof data[0] === "string") {
82
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
83
+ console.log(`${this.debugID} ${data[0]}`, ...data.slice(1));
84
+ } else {
85
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
86
+ console.log(this.debugID, ...data);
87
+ }
88
+ }
89
+
90
+ async initialize(handler: DriveUpdateErrorHandler) {
91
+ this.debugLog("initialize(...)");
92
+ // if network connect comes back online
93
+ // then triggers the listeners update
94
+ if (typeof window !== "undefined") {
95
+ window.addEventListener("online", () => {
96
+ this.triggerUpdate(false, { type: "local" }, handler).catch((error) => {
97
+ logger.error("Non handled error updating listeners", error);
98
+ });
99
+ });
100
+ }
56
101
  }
57
102
 
58
103
  driveHasListeners(driveId: string) {
59
- return this.listenerState.has(driveId);
104
+ return this.listenerStateByDriveId.has(driveId);
60
105
  }
61
106
 
62
- async addListener(listener: Listener) {
63
- const drive = listener.driveId;
107
+ async setListener(driveId: string, listener: Listener) {
108
+ this.debugLog(
109
+ `setListener(drive: ${driveId}, listener: ${listener.listenerId})`,
110
+ );
64
111
 
65
- if (!this.listenerState.has(drive)) {
66
- this.listenerState.set(drive, new Map());
112
+ // slight code smell -- drive id may not need to be on listener or not passed in
113
+ if (driveId !== listener.driveId) {
114
+ throw new Error("Drive ID mismatch");
67
115
  }
68
116
 
69
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
70
- const driveMap = this.listenerState.get(drive)!;
71
- driveMap.set(listener.listenerId, {
117
+ let existingState;
118
+ try {
119
+ existingState = this.getListenerState(driveId, listener.listenerId);
120
+ } catch {
121
+ existingState = {};
122
+ }
123
+
124
+ // keep existing state if it exists
125
+ this.setListenerState(driveId, listener.listenerId, {
126
+ ...existingState,
72
127
  block: listener.block,
73
128
  driveId: listener.driveId,
74
129
  pendingTimeout: "0",
@@ -77,38 +132,13 @@ export class ListenerManager extends BaseListenerManager {
77
132
  syncUnits: new Map(),
78
133
  });
79
134
 
80
- let transmitter: ITransmitter | undefined;
81
-
82
- switch (listener.callInfo?.transmitterType) {
83
- case "SwitchboardPush": {
84
- transmitter = new SwitchboardPushTransmitter(listener, this.drive);
85
- break;
86
- }
87
-
88
- case "PullResponder": {
89
- transmitter = new PullResponderTransmitter(listener, this.drive, this);
90
- break;
91
- }
92
- case "Internal": {
93
- transmitter = new InternalTransmitter(listener, this.drive);
94
- break;
95
- }
96
- }
97
-
98
- if (!transmitter) {
99
- throw new Error("Transmitter not found");
100
- }
101
-
102
- const driveTransmitters = this.transmitters[drive] || {};
103
- driveTransmitters[listener.listenerId] = transmitter;
104
- this.transmitters[drive] = driveTransmitters;
105
-
106
135
  this.triggerUpdate(true, { type: "local" });
107
- return Promise.resolve(transmitter);
108
136
  }
109
137
 
110
138
  async removeListener(driveId: string, listenerId: string) {
111
- const driveMap = this.listenerState.get(driveId);
139
+ this.debugLog("setListener()");
140
+
141
+ const driveMap = this.listenerStateByDriveId.get(driveId);
112
142
  if (!driveMap) {
113
143
  return false;
114
144
  }
@@ -119,8 +149,8 @@ export class ListenerManager extends BaseListenerManager {
119
149
  async removeSyncUnits(
120
150
  driveId: string,
121
151
  syncUnits: Pick<SynchronizationUnit, "syncId">[],
122
- ) {
123
- const listeners = this.listenerState.get(driveId);
152
+ ): Promise<void> {
153
+ const listeners = this.listenerStateByDriveId.get(driveId);
124
154
  if (!listeners) {
125
155
  return;
126
156
  }
@@ -140,38 +170,35 @@ export class ListenerManager extends BaseListenerManager {
140
170
  onError?: (error: Error, driveId: string, listener: ListenerState) => void,
141
171
  forceSync = false,
142
172
  ) {
143
- const drive = this.listenerState.get(driveId);
144
- if (!drive) {
173
+ const listenerIdToListenerState = this.listenerStateByDriveId.get(driveId);
174
+ if (!listenerIdToListenerState) {
145
175
  return [];
146
176
  }
147
177
 
148
178
  const outdatedListeners: Listener[] = [];
149
- for (const [, listener] of drive) {
179
+ for (const [, listenerState] of listenerIdToListenerState) {
150
180
  if (
151
181
  outdatedListeners.find(
152
- (l) => l.listenerId === listener.listener.listenerId,
182
+ (l) => l.listenerId === listenerState.listener.listenerId,
153
183
  )
154
184
  ) {
155
185
  continue;
156
186
  }
157
187
 
158
- const transmitter = await this.getTransmitter(
159
- driveId,
160
- listener.listener.listenerId,
161
- );
188
+ const transmitter = listenerState.listener.transmitter;
162
189
  if (!transmitter?.transmit) {
163
190
  continue;
164
191
  }
165
192
 
166
193
  for (const syncUnit of syncUnits) {
167
- if (!this._checkFilter(listener.listener.filter, syncUnit)) {
194
+ if (!this._checkFilter(listenerState.listener.filter, syncUnit)) {
168
195
  continue;
169
196
  }
170
197
 
171
- const listenerRev = listener.syncUnits.get(syncUnit.syncId);
198
+ const listenerRev = listenerState.syncUnits.get(syncUnit.syncId);
172
199
 
173
200
  if (!listenerRev || listenerRev.listenerRev < syncUnit.revision) {
174
- outdatedListeners.push(listener.listener);
201
+ outdatedListeners.push(listenerState.listener);
175
202
  break;
176
203
  }
177
204
  }
@@ -190,7 +217,7 @@ export class ListenerManager extends BaseListenerManager {
190
217
  syncId: string,
191
218
  listenerRev: number,
192
219
  ): Promise<void> {
193
- const drive = this.listenerState.get(driveId);
220
+ const drive = this.listenerStateByDriveId.get(driveId);
194
221
  if (!drive) {
195
222
  return;
196
223
  }
@@ -220,32 +247,51 @@ export class ListenerManager extends BaseListenerManager {
220
247
  private async _triggerUpdate(
221
248
  source: StrandUpdateSource,
222
249
  onError?: (error: Error, driveId: string, listener: ListenerState) => void,
250
+ maxContinues = 500,
223
251
  ) {
252
+ this.debugLog(
253
+ `_triggerUpdate(source: ${source.type}, maxContinues: ${maxContinues})`,
254
+ this.listenerStateByDriveId,
255
+ );
256
+
257
+ if (maxContinues < 0) {
258
+ throw new Error("Maximum retries exhausted.");
259
+ }
260
+
224
261
  const listenerUpdates: ListenerUpdate[] = [];
225
- for (const [driveId, drive] of this.listenerState) {
226
- for (const [id, listener] of drive) {
227
- const transmitter = await this.getTransmitter(driveId, id);
262
+
263
+ for (const [driveId, drive] of this.listenerStateByDriveId) {
264
+ for (const [listenerId, listenerState] of drive) {
265
+ const transmitter = listenerState.listener.transmitter;
266
+
228
267
  if (!transmitter?.transmit) {
268
+ this.debugLog(`Transmitter not set on listener: ${listenerId}`);
229
269
  continue;
230
270
  }
231
271
 
232
- const syncUnits = await this.getListenerSyncUnits(
233
- driveId,
234
- listener.listener.listenerId,
235
- );
236
-
272
+ const syncUnits = await this.getListenerSyncUnits(driveId, listenerId);
237
273
  const strandUpdates: StrandUpdate[] = [];
274
+
275
+ this.debugLog("syncUnits", syncUnits);
276
+
238
277
  // TODO change to push one after the other, reusing operation data
239
278
  const tasks = syncUnits.map((syncUnit) => async () => {
240
- const unitState = listener.syncUnits.get(syncUnit.syncId);
279
+ const unitState = listenerState.syncUnits.get(syncUnit.syncId);
241
280
 
242
281
  if (unitState && unitState.listenerRev >= syncUnit.revision) {
282
+ this.debugLog(
283
+ `Abandoning push for sync unit ${syncUnit.syncId}: already up-to-date (${unitState.listenerRev} >= ${syncUnit.revision})`,
284
+ );
243
285
  return;
286
+ } else {
287
+ this.debugLog(
288
+ `Listener out-of-date for sync unit ${syncUnit.syncId}: ${unitState?.listenerRev} < ${syncUnit.revision}`,
289
+ );
244
290
  }
245
291
 
246
292
  const opData: OperationUpdate[] = [];
247
293
  try {
248
- const data = await this.drive.getOperationData(
294
+ const data = await this.driveServer.getOperationData(
249
295
  // TODO - join queries, DEAL WITH INVALID SYNC ID ERROR
250
296
  driveId,
251
297
  syncUnit.syncId,
@@ -259,6 +305,9 @@ export class ListenerManager extends BaseListenerManager {
259
305
  }
260
306
 
261
307
  if (!opData.length) {
308
+ this.debugLog(
309
+ `Abandoning push for ${syncUnit.syncId}: no operations found`,
310
+ );
262
311
  return;
263
312
  }
264
313
 
@@ -270,34 +319,53 @@ export class ListenerManager extends BaseListenerManager {
270
319
  scope: syncUnit.scope as OperationScope,
271
320
  });
272
321
  });
322
+
273
323
  if (this.options.sequentialUpdates) {
324
+ this.debugLog(
325
+ `Collecting ${tasks.length} syncUnit strandUpdates in sequence`,
326
+ );
274
327
  for (const task of tasks) {
275
328
  await task();
276
329
  }
277
330
  } else {
331
+ this.debugLog(
332
+ `Collecting ${tasks.length} syncUnit strandUpdates in parallel`,
333
+ );
278
334
  await Promise.all(tasks.map((task) => task()));
279
335
  }
280
336
 
281
337
  if (strandUpdates.length == 0) {
338
+ this.debugLog(`No strandUpdates needed for listener ${listenerId}`);
282
339
  continue;
283
340
  }
284
341
 
285
- listener.pendingTimeout = new Date(
342
+ listenerState.pendingTimeout = new Date(
286
343
  new Date().getTime() / 1000 + 300,
287
344
  ).toISOString();
288
- listener.listenerStatus = "PENDING";
345
+
346
+ listenerState.listenerStatus = "PENDING";
289
347
 
290
348
  // TODO update listeners in parallel, blocking for listeners with block=true
291
349
  try {
350
+ this.debugLog(
351
+ `_triggerUpdate(source: ${source.type}) > transmitter.transmit`,
352
+ );
353
+
292
354
  const listenerRevisions = await transmitter.transmit(
293
355
  strandUpdates,
294
356
  source,
295
357
  );
296
358
 
297
- listener.pendingTimeout = "0";
298
- listener.listenerStatus = "PENDING";
359
+ this.debugLog(
360
+ `_triggerUpdate(source: ${source.type}) > transmission succeeded`,
361
+ listenerRevisions,
362
+ );
363
+
364
+ listenerState.pendingTimeout = "0";
365
+ listenerState.listenerStatus = "PENDING";
299
366
 
300
367
  const lastUpdated = new Date().toISOString();
368
+ let continuationNeeded = false;
301
369
 
302
370
  for (const revision of listenerRevisions) {
303
371
  const syncUnit = syncUnits.find(
@@ -306,14 +374,45 @@ export class ListenerManager extends BaseListenerManager {
306
374
  revision.scope === unit.scope &&
307
375
  revision.branch === unit.branch,
308
376
  );
377
+
309
378
  if (syncUnit) {
310
- listener.syncUnits.set(syncUnit.syncId, {
379
+ listenerState.syncUnits.set(syncUnit.syncId, {
311
380
  lastUpdated,
312
381
  listenerRev: revision.revision,
313
382
  });
383
+
384
+ // Check for revision status vv
385
+ const su = strandUpdates.find(
386
+ (su) =>
387
+ su.driveId === revision.driveId &&
388
+ su.documentId === revision.documentId &&
389
+ su.scope === revision.scope &&
390
+ su.branch === revision.branch,
391
+ );
392
+
393
+ if (su && su.operations.length > 0) {
394
+ const suIndex = su.operations.at(
395
+ su.operations.length - 1,
396
+ )?.index;
397
+ if (suIndex !== revision.revision) {
398
+ this.debugLog(
399
+ `Revision still out-of-date for ${su.documentId}:${su.scope}:${su.branch} ${suIndex} <> ${revision.revision}`,
400
+ );
401
+ continuationNeeded = true;
402
+ } else {
403
+ this.debugLog(
404
+ `Revision match for ${su.documentId}:${su.scope}:${su.branch} ${suIndex}`,
405
+ );
406
+ }
407
+ } else {
408
+ this.debugLog(
409
+ `Cannot find strand update for (${revision.documentId}:${revision.scope}:${revision.branch} in drive ${revision.driveId})`,
410
+ );
411
+ }
412
+ // Check for revision status ^^
314
413
  } else {
315
414
  logger.warn(
316
- `Received revision for untracked unit for listener ${listener.listener.listenerId}`,
415
+ `Received revision for untracked unit for listener ${listenerState.listener.listenerId}`,
317
416
  revision,
318
417
  );
319
418
  }
@@ -322,32 +421,46 @@ export class ListenerManager extends BaseListenerManager {
322
421
  for (const revision of listenerRevisions) {
323
422
  const error = revision.status === "ERROR";
324
423
  if (revision.error?.includes("Missing operations")) {
325
- const updates = await this._triggerUpdate(source, onError);
326
- listenerUpdates.push(...updates);
327
- } else {
328
- listenerUpdates.push({
329
- listenerId: listener.listener.listenerId,
330
- listenerRevisions,
331
- });
332
- if (error) {
333
- throw new OperationError(
334
- revision.status as ErrorStatus,
335
- undefined,
336
- revision.error,
337
- revision.error,
338
- );
339
- }
424
+ continuationNeeded = true;
425
+ } else if (error) {
426
+ throw new OperationError(
427
+ revision.status as ErrorStatus,
428
+ undefined,
429
+ revision.error,
430
+ revision.error,
431
+ );
340
432
  }
341
433
  }
342
- listener.listenerStatus = "SUCCESS";
434
+
435
+ if (!continuationNeeded) {
436
+ listenerUpdates.push({
437
+ listenerId: listenerState.listener.listenerId,
438
+ listenerRevisions,
439
+ });
440
+ } else {
441
+ const updates = await this._triggerUpdate(
442
+ source,
443
+ onError,
444
+ maxContinues - 1,
445
+ );
446
+ listenerUpdates.push(...updates);
447
+ }
448
+
449
+ listenerState.listenerStatus = "SUCCESS";
343
450
  } catch (e) {
344
451
  // TODO: Handle error based on listener params (blocking, retry, etc)
345
- onError?.(e as Error, driveId, listener);
346
- listener.listenerStatus =
452
+ onError?.(e as Error, driveId, listenerState);
453
+ listenerState.listenerStatus =
347
454
  e instanceof OperationError ? e.status : "ERROR";
348
455
  }
349
456
  }
350
457
  }
458
+
459
+ this.debugLog(
460
+ `Returning listener updates (maxContinues: ${maxContinues})`,
461
+ listenerUpdates,
462
+ );
463
+
351
464
  return listenerUpdates;
352
465
  }
353
466
 
@@ -378,12 +491,12 @@ export class ListenerManager extends BaseListenerManager {
378
491
  listenerId: string,
379
492
  loadedDrive?: DocumentDriveDocument,
380
493
  ) {
381
- const listener = this.listenerState.get(driveId)?.get(listenerId);
494
+ const listener = this.listenerStateByDriveId.get(driveId)?.get(listenerId);
382
495
  if (!listener) {
383
496
  return [];
384
497
  }
385
498
  const filter = listener.listener.filter;
386
- return this.drive.getSynchronizationUnits(
499
+ return this.driveServer.getSynchronizationUnits(
387
500
  driveId,
388
501
  filter.documentId ?? ["*"],
389
502
  filter.scope ?? ["*"],
@@ -393,13 +506,16 @@ export class ListenerManager extends BaseListenerManager {
393
506
  );
394
507
  }
395
508
 
396
- getListenerSyncUnitIds(driveId: string, listenerId: string) {
397
- const listener = this.listenerState.get(driveId)?.get(listenerId);
509
+ getListenerSyncUnitIds(
510
+ driveId: string,
511
+ listenerId: string,
512
+ ): Promise<SynchronizationUnitQuery[]> {
513
+ const listener = this.listenerStateByDriveId.get(driveId)?.get(listenerId);
398
514
  if (!listener) {
399
- return [];
515
+ return Promise.resolve([]);
400
516
  }
401
517
  const filter = listener.listener.filter;
402
- return this.drive.getSynchronizationUnitsIds(
518
+ return this.driveServer.getSynchronizationUnitsIds(
403
519
  driveId,
404
520
  filter.documentId ?? ["*"],
405
521
  filter.scope ?? ["*"],
@@ -408,47 +524,23 @@ export class ListenerManager extends BaseListenerManager {
408
524
  );
409
525
  }
410
526
 
411
- async initDrive(drive: DocumentDriveDocument) {
412
- const {
413
- state: {
414
- local: { listeners },
415
- },
416
- } = drive;
417
-
418
- for (const listener of listeners) {
419
- await this.addListener({
420
- block: listener.block,
421
- driveId: drive.state.global.id,
422
- filter: {
423
- branch: listener.filter.branch ?? [],
424
- documentId: listener.filter.documentId ?? [],
425
- documentType: listener.filter.documentType,
426
- scope: listener.filter.scope ?? [],
427
- },
428
- listenerId: listener.listenerId,
429
- system: listener.system,
430
- callInfo: listener.callInfo ?? undefined,
431
- label: listener.label ?? "",
432
- });
433
- }
434
- }
435
-
436
527
  async removeDrive(driveId: string): Promise<void> {
437
- this.listenerState.delete(driveId);
438
- const transmitters = this.transmitters[driveId];
439
- if (transmitters) {
440
- await Promise.all(
441
- Object.values(transmitters).map((t) => t.disconnect?.()),
442
- );
528
+ const listenerIdToListenerState = this.listenerStateByDriveId.get(driveId);
529
+ if (!listenerIdToListenerState) {
530
+ return;
443
531
  }
444
- }
445
532
 
446
- getListener(driveId: string, listenerId: string): Promise<ListenerState> {
447
- const drive = this.listenerState.get(driveId);
448
- if (!drive) throw new Error("Drive not found");
449
- const listener = drive.get(listenerId);
450
- if (!listener) throw new Error("Listener not found");
451
- return Promise.resolve(listener);
533
+ // delete first
534
+ this.listenerStateByDriveId.delete(driveId);
535
+
536
+ for (const [_, listenerState] of listenerIdToListenerState) {
537
+ // guarantee that all disconnects are called
538
+ try {
539
+ await listenerState.listener.transmitter?.disconnect?.();
540
+ } catch (error) {
541
+ logger.error(error);
542
+ }
543
+ }
452
544
  }
453
545
 
454
546
  async getStrands(
@@ -456,13 +548,13 @@ export class ListenerManager extends BaseListenerManager {
456
548
  listenerId: string,
457
549
  options?: GetStrandsOptions,
458
550
  ): Promise<StrandUpdate[]> {
459
- // fetch listenerState from listenerManager
460
- const listener = await this.getListener(driveId, listenerId);
551
+ // this will throw if listenerState is not found
552
+ const listenerState = this.getListenerState(driveId, listenerId);
461
553
 
462
554
  // fetch operations from drive and prepare strands
463
555
  const strands: StrandUpdate[] = [];
464
556
 
465
- const drive = await this.drive.getDrive(driveId);
557
+ const drive = await this.driveServer.getDrive(driveId);
466
558
  const syncUnits = await this.getListenerSyncUnits(
467
559
  driveId,
468
560
  listenerId,
@@ -480,14 +572,14 @@ export class ListenerManager extends BaseListenerManager {
480
572
  if (syncUnit.revision < 0) {
481
573
  return;
482
574
  }
483
- const entry = listener.syncUnits.get(syncUnit.syncId);
575
+ const entry = listenerState.syncUnits.get(syncUnit.syncId);
484
576
  if (entry && entry.listenerRev >= syncUnit.revision) {
485
577
  return;
486
578
  }
487
579
 
488
580
  const { documentId, driveId, scope, branch } = syncUnit;
489
581
  try {
490
- const operations = await this.drive.getOperationData(
582
+ const operations = await this.driveServer.getOperationData(
491
583
  // DEAL WITH INVALID SYNC ID ERROR
492
584
  driveId,
493
585
  syncUnit.syncId,
@@ -528,4 +620,33 @@ export class ListenerManager extends BaseListenerManager {
528
620
 
529
621
  return strands;
530
622
  }
623
+
624
+ getListenerState(driveId: string, listenerId: string) {
625
+ let listenerStateByListenerId = this.listenerStateByDriveId.get(driveId);
626
+ if (!listenerStateByListenerId) {
627
+ listenerStateByListenerId = new Map();
628
+ this.listenerStateByDriveId.set(driveId, listenerStateByListenerId);
629
+ }
630
+
631
+ const listenerState = listenerStateByListenerId.get(listenerId);
632
+ if (!listenerState) {
633
+ throw new Error("Listener not found");
634
+ }
635
+
636
+ return listenerState;
637
+ }
638
+
639
+ setListenerState(
640
+ driveId: string,
641
+ listenerId: string,
642
+ listenerState: ListenerState,
643
+ ) {
644
+ let listenerStateByListenerId = this.listenerStateByDriveId.get(driveId);
645
+ if (!listenerStateByListenerId) {
646
+ listenerStateByListenerId = new Map();
647
+ this.listenerStateByDriveId.set(driveId, listenerStateByListenerId);
648
+ }
649
+
650
+ listenerStateByListenerId.set(listenerId, listenerState);
651
+ }
531
652
  }