@astral/mobx-query 1.15.1 → 1.16.1

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.
Files changed (42) hide show
  1. package/MobxQuery/MobxQuery.d.ts +13 -8
  2. package/MobxQuery/MobxQuery.js +19 -18
  3. package/MobxQuery/types.d.ts +22 -0
  4. package/MobxQuery/types.js +1 -0
  5. package/PollingService/PollingService.d.ts +34 -0
  6. package/PollingService/PollingService.js +95 -0
  7. package/PollingService/index.d.ts +1 -0
  8. package/PollingService/index.js +1 -0
  9. package/README.md +22 -0
  10. package/SynchronizationService/SynchronizationService.d.ts +5 -5
  11. package/SynchronizationService/SynchronizationService.js +7 -9
  12. package/__test/BroadcastChannel/BroadcastChannel.d.ts +1 -0
  13. package/__test/BroadcastChannel/BroadcastChannel.js +13 -0
  14. package/__test/BroadcastChannel/index.d.ts +1 -0
  15. package/__test/BroadcastChannel/index.js +1 -0
  16. package/__test/documentMock/documentMock.d.ts +380 -0
  17. package/__test/documentMock/documentMock.js +18 -0
  18. package/__test/documentMock/index.d.ts +1 -0
  19. package/__test/documentMock/index.js +1 -0
  20. package/__test/index.d.ts +2 -0
  21. package/__test/index.js +2 -0
  22. package/node/MobxQuery/MobxQuery.d.ts +13 -8
  23. package/node/MobxQuery/MobxQuery.js +19 -18
  24. package/node/MobxQuery/types.d.ts +22 -0
  25. package/node/MobxQuery/types.js +2 -0
  26. package/node/PollingService/PollingService.d.ts +34 -0
  27. package/node/PollingService/PollingService.js +99 -0
  28. package/node/PollingService/index.d.ts +1 -0
  29. package/node/PollingService/index.js +17 -0
  30. package/node/SynchronizationService/SynchronizationService.d.ts +5 -5
  31. package/node/SynchronizationService/SynchronizationService.js +7 -9
  32. package/node/__test/BroadcastChannel/BroadcastChannel.d.ts +1 -0
  33. package/node/__test/BroadcastChannel/BroadcastChannel.js +17 -0
  34. package/node/__test/BroadcastChannel/index.d.ts +1 -0
  35. package/node/__test/BroadcastChannel/index.js +17 -0
  36. package/node/__test/documentMock/documentMock.d.ts +380 -0
  37. package/node/__test/documentMock/documentMock.js +22 -0
  38. package/node/__test/documentMock/index.d.ts +1 -0
  39. package/node/__test/documentMock/index.js +17 -0
  40. package/node/__test/index.d.ts +2 -0
  41. package/node/__test/index.js +18 -0
  42. package/package.json +1 -1
@@ -3,6 +3,7 @@ import { Mutation, type MutationExecutor, type MutationParams } from '../Mutatio
3
3
  import { Query, type QueryExecutor, type QueryParams } from '../Query';
4
4
  import { InfiniteQuerySet, type InfiniteQuerySetConfig, type InfiniteQuerySetConfigurator, MutationSet, QuerySet, type QuerySetConfig, type QuerySetConfigurator } from '../Sets';
5
5
  import type { CacheKey, FetchPolicy } from '../types';
6
+ import type { UnknownCachedQuery } from './types';
6
7
  /**
7
8
  * Стандартный обработчик ошибки запроса,
8
9
  * будет вызван, если при вызове sync не был передан отдельный onError параметр
@@ -15,6 +16,12 @@ type WithSynchronization = {
15
16
  */
16
17
  enabledSynchronization?: boolean;
17
18
  };
19
+ type WithPollingTime = {
20
+ /**
21
+ * Время в мс, раз в которое необходимо инвалидировать query
22
+ */
23
+ pollingTime?: number;
24
+ };
18
25
  type MobxQueryParams = {
19
26
  /**
20
27
  * Политика получения данных по умолчанию.
@@ -43,19 +50,15 @@ export type CreateQueryParams<TResult, TError, TIsBackground extends boolean> =
43
50
  * @default false
44
51
  */
45
52
  isBackground?: TIsBackground;
46
- } & WithSynchronization;
53
+ } & WithSynchronization & WithPollingTime;
47
54
  export type CreateInfiniteQueryParams<TResult, TError, TIsBackground extends boolean> = Omit<InfiniteQueryParams<TResult, TError, TIsBackground>, 'dataStorage' | 'statusStorage' | 'backgroundStatusStorage' | 'submitValidity'> & {
48
55
  /**
49
56
  * Режим фонового обновления
50
57
  * @default false
51
58
  */
52
59
  isBackground?: TIsBackground;
53
- } & WithSynchronization;
60
+ } & WithSynchronization & WithPollingTime;
54
61
  export type CreateMutationParams<TResult, TError> = MutationParams<TResult, TError>;
55
- /**
56
- * Внутриний тип кешируемого стора
57
- */
58
- type CachedQuery<TResult, TError, TIsBackground extends boolean> = Query<TResult, TError, TIsBackground> | InfiniteQuery<TResult, TError, TIsBackground>;
59
62
  /**
60
63
  * Стратегия инвалидации кэша
61
64
  * @property 'partial-match' - Проверяет наличие хотя бы одной части ключа в кэше
@@ -97,11 +100,12 @@ export declare class MobxQuery<TDefaultError = void> {
97
100
  private readonly defaultEnabledAutoFetch;
98
101
  private readonly defauleEnabledSynchronization;
99
102
  private readonly synchronizationService;
103
+ private readonly pollingService;
100
104
  private serialize;
101
105
  constructor({ onError, fetchPolicy, enabledAutoFetch, enabledSynchronization, }?: MobxQueryParams, _BroadcastChannel?: {
102
106
  new (name: string): BroadcastChannel;
103
107
  prototype: BroadcastChannel;
104
- });
108
+ }, _document?: Document);
105
109
  /**
106
110
  * Проверяет пересечение ключей в зависимости от стратегии
107
111
  * @param invalidatedKeys - Ключи для инвалидации
@@ -123,6 +127,7 @@ export declare class MobxQuery<TDefaultError = void> {
123
127
  * mobxQuery.invalidate(['users', '1'], 'chain-match'); // Инвалидирует только query с точным набором ключей ['users', '1']
124
128
  */
125
129
  invalidate: (invalidatedKeys: CacheKey[], strategy?: InvalidateStrategy) => void;
130
+ private invalidateByKeyHash;
126
131
  /**
127
132
  * Метод для получения массива хешей ключей на основании ключей и стратегии
128
133
  */
@@ -130,7 +135,7 @@ export declare class MobxQuery<TDefaultError = void> {
130
135
  /**
131
136
  * метод для получения массива существующий квери на основании ключей и стратегии
132
137
  */
133
- getExistsQueries: <TQuery = CachedQuery<unknown, unknown, false>>(keys: CacheKey[], strategy?: InvalidateStrategy) => TQuery[];
138
+ getExistsQueries: <TQuery = UnknownCachedQuery>(keys: CacheKey[], strategy?: InvalidateStrategy) => TQuery[];
134
139
  private submitValidity;
135
140
  /**
136
141
  * Метод инвалидации всех query
@@ -2,6 +2,7 @@ import { AdaptableMap } from '../AdaptableMap';
2
2
  import { DataStorageFactory } from '../DataStorage';
3
3
  import { InfiniteQuery, } from '../InfiniteQuery';
4
4
  import { Mutation, } from '../Mutation';
5
+ import { PollingService } from '../PollingService';
5
6
  import { Query } from '../Query';
6
7
  import { InfiniteQuerySet, MutationSet, QuerySet, } from '../Sets';
7
8
  import { StatusStorageFactory } from '../StatusStorage';
@@ -10,7 +11,7 @@ import { SynchronizationService } from '../SynchronizationService';
10
11
  * Сервис, позволяющий кэшировать данные.
11
12
  */
12
13
  export class MobxQuery {
13
- constructor({ onError, fetchPolicy = 'cache-first', enabledAutoFetch = false, enabledSynchronization = false, } = {}, _BroadcastChannel = globalThis.BroadcastChannel) {
14
+ constructor({ onError, fetchPolicy = 'cache-first', enabledAutoFetch = false, enabledSynchronization = false, } = {}, _BroadcastChannel = globalThis.BroadcastChannel, _document = globalThis.document) {
14
15
  /**
15
16
  * Объект соответствия хешей ключей и их значений
16
17
  */
@@ -55,13 +56,15 @@ export class MobxQuery {
55
56
  * mobxQuery.invalidate(['users', '1'], 'chain-match'); // Инвалидирует только query с точным набором ключей ['users', '1']
56
57
  */
57
58
  this.invalidate = (invalidatedKeys, strategy = 'partial-match') => {
58
- this.getExistsKeyHashes(invalidatedKeys, strategy).forEach(({ keyHash }) => {
59
- var _a;
60
- (_a = this.queriesMap.get(keyHash)) === null || _a === void 0 ? void 0 : _a.invalidate();
61
- // Конвертируем инвалидированный квери в слабый,
62
- // чтобы сборщик мусора мог удалить неиспользуемые квери
63
- this.queriesMap.convertToWeak(keyHash);
64
- });
59
+ this.getExistsKeyHashes(invalidatedKeys, strategy).forEach(({ keyHash }) => this.invalidateByKeyHash(keyHash));
60
+ };
61
+ this.invalidateByKeyHash = (keyHash) => {
62
+ var _a;
63
+ (_a = this.queriesMap.get(keyHash)) === null || _a === void 0 ? void 0 : _a.invalidate();
64
+ // Конвертируем инвалидированный квери в слабый,
65
+ // чтобы сборщик мусора мог удалить неиспользуемые квери
66
+ this.queriesMap.convertToWeak(keyHash);
67
+ this.pollingService.clean(keyHash);
65
68
  };
66
69
  /**
67
70
  * Метод для получения массива хешей ключей на основании ключей и стратегии
@@ -78,10 +81,13 @@ export class MobxQuery {
78
81
  */
79
82
  this.getExistsQueries = (keys, strategy = 'partial-match') => this.getExistsKeyHashes(keys, strategy).map(({ keyHash }) => this.queriesMap.get(keyHash));
80
83
  // Метод для подтверждения того, что квери успешно получил валидные данные
81
- this.submitValidity = (keys, isNetworkOnly, enabledSynchronization) => {
84
+ this.submitValidity = (keys, isNetworkOnly, enabledSynchronization, pollingTime) => {
82
85
  if (enabledSynchronization) {
83
86
  this.synchronizationService.emit(keys);
84
87
  }
88
+ if (pollingTime) {
89
+ this.pollingService.setupTimer(keys.queryKeyHash, pollingTime);
90
+ }
85
91
  // 'network-only' квери не будут конвертироваться,
86
92
  // следовательно, они всегда будут храниться как "слабые",
87
93
  // что позволит сборщику мусора удалять их из памяти при отсутствии ссылок
@@ -95,13 +101,7 @@ export class MobxQuery {
95
101
  /**
96
102
  * Метод инвалидации всех query
97
103
  */
98
- this.invalidateQueries = () => {
99
- [...this.keys.keys()].forEach((keyHash) => {
100
- var _a;
101
- (_a = this.queriesMap.get(keyHash)) === null || _a === void 0 ? void 0 : _a.invalidate();
102
- this.queriesMap.convertToWeak(keyHash);
103
- });
104
- };
104
+ this.invalidateQueries = () => [...this.keys.keys()].forEach(this.invalidateByKeyHash);
105
105
  /**
106
106
  * Метод, который занимается проверкой наличия квери по ключу,
107
107
  * и если нет, создает новый, добавляет его к себе в память, и возвращает его пользователю
@@ -124,7 +124,7 @@ export class MobxQuery {
124
124
  backgroundStatusStorage: this.getBackgroundStatusStorage(keys.backgroundStatusKeyHash, Boolean(createParams === null || createParams === void 0 ? void 0 : createParams.isBackground)),
125
125
  submitValidity: () => {
126
126
  var _a;
127
- return this.submitValidity(keys, fetchPolicy !== 'network-only', (_a = createParams === null || createParams === void 0 ? void 0 : createParams.enabledSynchronization) !== null && _a !== void 0 ? _a : this.defauleEnabledSynchronization);
127
+ return this.submitValidity(keys, fetchPolicy !== 'network-only', (_a = createParams === null || createParams === void 0 ? void 0 : createParams.enabledSynchronization) !== null && _a !== void 0 ? _a : this.defauleEnabledSynchronization, createParams === null || createParams === void 0 ? void 0 : createParams.pollingTime);
128
128
  },
129
129
  });
130
130
  this.queriesMap.set(keys.queryKeyHash, query);
@@ -228,6 +228,7 @@ export class MobxQuery {
228
228
  this.defaultFetchPolicy = fetchPolicy;
229
229
  this.defaultEnabledAutoFetch = enabledAutoFetch;
230
230
  this.defauleEnabledSynchronization = enabledSynchronization;
231
- this.synchronizationService = new SynchronizationService(this.statusStorageFactory, this.queryDataStorageFactory, _BroadcastChannel);
231
+ this.pollingService = new PollingService(this.queriesMap, this.invalidateByKeyHash, _document);
232
+ this.synchronizationService = new SynchronizationService(this.statusStorageFactory, this.queryDataStorageFactory, this.pollingService, _BroadcastChannel);
232
233
  }
233
234
  }
@@ -0,0 +1,22 @@
1
+ import { type InfiniteQuery } from '../InfiniteQuery';
2
+ import { type Query } from '../Query';
3
+ import { type CacheKey } from '../types';
4
+ /**
5
+ * Внутриний тип кешируемого стора
6
+ */
7
+ export type CachedQuery<TResult, TError, TIsBackground extends boolean> = Query<TResult, TError, TIsBackground> | InfiniteQuery<TResult, TError, TIsBackground>;
8
+ export type UnknownCachedQuery = CachedQuery<unknown, unknown, false>;
9
+ /**
10
+ * Хэш ключа
11
+ */
12
+ export type KeyHash = string;
13
+ /**
14
+ * Набор ключей
15
+ */
16
+ export type Keys = {
17
+ queryKey: CacheKey[];
18
+ dataKeyHash: KeyHash;
19
+ statusKeyHash: KeyHash;
20
+ queryKeyHash: KeyHash;
21
+ backgroundStatusKeyHash: KeyHash;
22
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,34 @@
1
+ import { type AdaptableMap } from '../AdaptableMap';
2
+ import { type KeyHash, type UnknownCachedQuery } from '../MobxQuery/types';
3
+ /**
4
+ * Сущность для инкапсулирования логики работы с устареванием данных
5
+ */
6
+ export declare class PollingService {
7
+ private readonly _queryStorage;
8
+ private readonly _invalidateByKeyHash;
9
+ private readonly _document;
10
+ private timers;
11
+ private timerDates;
12
+ private isVisible;
13
+ constructor(_queryStorage: AdaptableMap<UnknownCachedQuery>, _invalidateByKeyHash: (keyHash: KeyHash) => void, _document?: Document);
14
+ private updateIsVisible;
15
+ private init;
16
+ private handleVisibilityChange;
17
+ private restartPausedTimers;
18
+ private pauseTimers;
19
+ /**
20
+ * Очистка таймеров,
21
+ * предполагается использование либо при срабатывании таймера,
22
+ * либо при досрочном вызове из внешней инвалидации
23
+ */
24
+ clean: (key: KeyHash) => void;
25
+ /**
26
+ * Перезапуск имеющегося таймера, предполагается использование при срабатывании синхронизации между вкладками
27
+ */
28
+ restart: (key: KeyHash) => void;
29
+ private invalidate;
30
+ /**
31
+ * Установка таймера устаревания данных
32
+ */
33
+ setupTimer: (key: KeyHash, pollingTime: number) => void;
34
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Сущность для инкапсулирования логики работы с устареванием данных
3
+ */
4
+ export class PollingService {
5
+ constructor(_queryStorage, _invalidateByKeyHash, _document = globalThis.document) {
6
+ this._queryStorage = _queryStorage;
7
+ this._invalidateByKeyHash = _invalidateByKeyHash;
8
+ this._document = _document;
9
+ this.timers = new Map();
10
+ this.timerDates = new Map();
11
+ this.isVisible = true;
12
+ this.updateIsVisible = () => {
13
+ var _a;
14
+ this.isVisible = ((_a = this._document) === null || _a === void 0 ? void 0 : _a.visibilityState) === 'visible';
15
+ };
16
+ this.init = () => {
17
+ var _a;
18
+ this.updateIsVisible();
19
+ (_a = this._document) === null || _a === void 0 ? void 0 : _a.addEventListener('visibilitychange', this.handleVisibilityChange);
20
+ };
21
+ this.handleVisibilityChange = () => {
22
+ this.updateIsVisible();
23
+ if (this.isVisible) {
24
+ this.restartPausedTimers();
25
+ }
26
+ else {
27
+ this.pauseTimers();
28
+ }
29
+ };
30
+ this.restartPausedTimers = () => {
31
+ this.timerDates.keys().forEach((key) => {
32
+ const dateNow = Date.now();
33
+ const { pollingTime, targetDate } = this.timerDates.get(key);
34
+ const isAlreadyExpired = targetDate < dateNow;
35
+ if (isAlreadyExpired) {
36
+ this.invalidate(key);
37
+ }
38
+ else {
39
+ this.setupTimer(key, pollingTime);
40
+ }
41
+ });
42
+ };
43
+ this.pauseTimers = () => {
44
+ this.timers.keys().forEach((key) => {
45
+ globalThis.clearTimeout(this.timers.get(key));
46
+ this.timers.delete(key);
47
+ });
48
+ };
49
+ /**
50
+ * Очистка таймеров,
51
+ * предполагается использование либо при срабатывании таймера,
52
+ * либо при досрочном вызове из внешней инвалидации
53
+ */
54
+ this.clean = (key) => {
55
+ globalThis.clearTimeout(this.timers.get(key));
56
+ this.timers.delete(key);
57
+ this.timerDates.delete(key);
58
+ };
59
+ /**
60
+ * Перезапуск имеющегося таймера, предполагается использование при срабатывании синхронизации между вкладками
61
+ */
62
+ this.restart = (key) => {
63
+ const saved = this.timerDates.get(key);
64
+ if (saved) {
65
+ this.setupTimer(key, saved.pollingTime);
66
+ }
67
+ };
68
+ this.invalidate = (key) => {
69
+ const query = this._queryStorage.get(key);
70
+ if (query) {
71
+ this._invalidateByKeyHash(key);
72
+ }
73
+ this.clean(key);
74
+ };
75
+ /**
76
+ * Установка таймера устаревания данных
77
+ */
78
+ this.setupTimer = (key, pollingTime) => {
79
+ if (this.timers.has(key)) {
80
+ globalThis.clearTimeout(this.timers.get(key));
81
+ }
82
+ if (!this._queryStorage.get(key)) {
83
+ return;
84
+ }
85
+ this.timerDates.set(key, {
86
+ pollingTime,
87
+ targetDate: Date.now() + pollingTime,
88
+ });
89
+ if (this.isVisible) {
90
+ this.timers.set(key, globalThis.setTimeout(() => this.invalidate(key), pollingTime));
91
+ }
92
+ };
93
+ this.init();
94
+ }
95
+ }
@@ -0,0 +1 @@
1
+ export * from './PollingService';
@@ -0,0 +1 @@
1
+ export * from './PollingService';
package/README.md CHANGED
@@ -1098,6 +1098,28 @@ export type InfiniteQueryConfig = {
1098
1098
  };
1099
1099
  ```
1100
1100
 
1101
+ ### Переодическая инвалидация (Поллинг)
1102
+
1103
+ Для обеспечения автоматической инвалидации данных по указанному времени используйте настройку `pollingTime` при создании query.
1104
+ Значение необходимо указывать в милисекундах.
1105
+
1106
+ ```ts
1107
+ const TEN_MINUTES = 10 * 60 * 1000;
1108
+
1109
+ docsfetcher.queries.document.createWithConfig(
1110
+ {
1111
+ pollingTime: TEN_MINUTES,
1112
+ },
1113
+ documenId,
1114
+ );
1115
+
1116
+ docsfetcher.infiniteQueries.list.createWithConfig(
1117
+ {
1118
+ pollingTime: TEN_MINUTES,
1119
+ },
1120
+ );
1121
+ ```
1122
+
1101
1123
  ### Mutation
1102
1124
 
1103
1125
  `Mutation` предназначен для отправки данных на сервер с целью произведения изменений.
@@ -1,16 +1,16 @@
1
1
  import { type DataStorageFactory } from '../DataStorage';
2
+ import { type Keys } from '../MobxQuery/types';
3
+ import { type PollingService } from '../PollingService';
2
4
  import { type StatusStorageFactory } from '../StatusStorage';
3
5
  export declare class SynchronizationService {
4
6
  private readonly _statusStorageFactory;
5
7
  private readonly _dataStorageFactory;
8
+ private readonly _pollingService;
6
9
  private readonly broadcastChannel?;
7
- constructor(_statusStorageFactory: StatusStorageFactory, _dataStorageFactory: DataStorageFactory, _BroadcastChannel?: {
10
+ constructor(_statusStorageFactory: StatusStorageFactory, _dataStorageFactory: DataStorageFactory, _pollingService: PollingService, _BroadcastChannel?: {
8
11
  new (name: string): BroadcastChannel;
9
12
  prototype: BroadcastChannel;
10
13
  });
11
14
  private init;
12
- emit: (keys: {
13
- dataKeyHash: string;
14
- statusKeyHash: string;
15
- }) => void;
15
+ emit: (keys: Keys) => void;
16
16
  }
@@ -1,25 +1,23 @@
1
1
  export class SynchronizationService {
2
- constructor(_statusStorageFactory, _dataStorageFactory, _BroadcastChannel = globalThis.BroadcastChannel) {
2
+ constructor(_statusStorageFactory, _dataStorageFactory, _pollingService, _BroadcastChannel = globalThis.BroadcastChannel) {
3
3
  this._statusStorageFactory = _statusStorageFactory;
4
4
  this._dataStorageFactory = _dataStorageFactory;
5
+ this._pollingService = _pollingService;
5
6
  this.init = () => {
6
7
  var _a;
7
8
  (_a = this.broadcastChannel) === null || _a === void 0 ? void 0 : _a.addEventListener('message', (event) => {
8
9
  var _a, _b;
10
+ const data = JSON.parse(event.data);
9
11
  (_a = this._dataStorageFactory
10
- .getStorage(event.data.dataKeyHash)) === null || _a === void 0 ? void 0 : _a.setData(event.data.data);
12
+ .getStorage(data.dataKeyHash)) === null || _a === void 0 ? void 0 : _a.setData(data.data);
11
13
  (_b = this._statusStorageFactory
12
- .getStorage(event.data.statusKeyHash)) === null || _b === void 0 ? void 0 : _b.setStatues(event.data.statuses);
14
+ .getStorage(data.statusKeyHash)) === null || _b === void 0 ? void 0 : _b.setStatues(data.statuses);
15
+ this._pollingService.restart(data.queryKeyHash);
13
16
  });
14
17
  };
15
18
  this.emit = (keys) => {
16
19
  var _a, _b, _c;
17
- (_a = this.broadcastChannel) === null || _a === void 0 ? void 0 : _a.postMessage({
18
- dataKeyHash: keys.dataKeyHash,
19
- statusKeyHash: keys.statusKeyHash,
20
- data: (_b = this._dataStorageFactory.getStorage(keys.dataKeyHash)) === null || _b === void 0 ? void 0 : _b.data,
21
- statuses: (_c = this._statusStorageFactory.getStorage(keys.statusKeyHash)) === null || _c === void 0 ? void 0 : _c.statuses,
22
- });
20
+ (_a = this.broadcastChannel) === null || _a === void 0 ? void 0 : _a.postMessage(JSON.stringify(Object.assign(Object.assign({}, keys), { data: (_b = this._dataStorageFactory.getStorage(keys.dataKeyHash)) === null || _b === void 0 ? void 0 : _b.data, statuses: (_c = this._statusStorageFactory.getStorage(keys.statusKeyHash)) === null || _c === void 0 ? void 0 : _c.statuses })));
23
21
  };
24
22
  if (_BroadcastChannel) {
25
23
  this.broadcastChannel = new _BroadcastChannel('@astral/mobx-query');
@@ -0,0 +1 @@
1
+ export declare const createBroadcastChannelMock: () => typeof globalThis.BroadcastChannel;
@@ -0,0 +1,13 @@
1
+ // Мок не предусматривает полного повторения функционала оригинального BroadcastChannelMock
2
+ // поэтому он будет вызывать даже подписчиков вкладки инициатора,
3
+ // но благодаря общему подходу, ожидается, что это не вызовет проблем
4
+ export const createBroadcastChannelMock = () => {
5
+ const listeners = [];
6
+ class BroadcastChannelMock {
7
+ constructor() {
8
+ this.addEventListener = (_, listener) => listeners.push(listener);
9
+ this.postMessage = (data) => listeners.forEach((listener) => listener({ data }));
10
+ }
11
+ }
12
+ return BroadcastChannelMock;
13
+ };
@@ -0,0 +1 @@
1
+ export * from './BroadcastChannel';
@@ -0,0 +1 @@
1
+ export * from './BroadcastChannel';