blob: 101ce8a67ba8d6f9ff1d03a1f8175f002ed35242 [file] [log] [blame]
Adrià Vilanova Martínezf7e86852024-05-11 14:16:38 +02001import { Mutex, MutexInterface, withTimeout } from 'async-mutex';
2
Adrià Vilanova Martínez7c941c72024-10-26 20:23:34 +02003import { getOptions } from '../../../common/options/optionsUtils';
Adrià Vilanova Martínez9326a002025-02-08 23:16:12 +01004import {
5 OptionCodename,
6 OptionsValues,
7} from '../../../common/options/optionsPrototype';
Adrià Vilanova Martínez7c941c72024-10-26 20:23:34 +02008import { OptionsConfiguration } from '../../../common/options/OptionsConfiguration';
Adrià Vilanova Martínez6ecaa0d2024-10-26 17:04:32 +02009import {
10 OptionsChangeListener,
11 OptionsProviderPort,
Adrià Vilanova Martínez7c941c72024-10-26 20:23:34 +020012} from '../../../services/options/OptionsProvider';
Adrià Vilanova Martínezf7e86852024-05-11 14:16:38 +020013
Adrià Vilanova Martínezad696762024-05-25 19:32:36 +020014// Prioritize reads before writes.
15const kReadPriority = 10;
16const kWritePriority = 0;
17
18/**
19 * Class which provides option values and a way to listen to option changes.
20 */
Adrià Vilanova Martínez6ecaa0d2024-10-26 17:04:32 +020021export default class OptionsProviderAdapter implements OptionsProviderPort {
Adrià Vilanova Martínezad696762024-05-25 19:32:36 +020022 private optionsConfiguration: OptionsConfiguration;
Adrià Vilanova Martínezf7e86852024-05-11 14:16:38 +020023 private mutex: MutexInterface = withTimeout(new Mutex(), 60 * 1000);
Adrià Vilanova Martínezad696762024-05-25 19:32:36 +020024 private listeners: Set<OptionsChangeListener> = new Set();
Adrià Vilanova Martínez6ecaa0d2024-10-26 17:04:32 +020025 private isSetUp = false;
Adrià Vilanova Martínezf7e86852024-05-11 14:16:38 +020026
Adrià Vilanova Martínez6ecaa0d2024-10-26 17:04:32 +020027 async getOptionValue<O extends OptionCodename>(
28 option: O,
29 ): Promise<OptionsValues[O]> {
30 this.setUp();
31 return this.mutex.runExclusive(
32 () => this.optionsConfiguration.getOptionValue(option),
33 kReadPriority,
34 );
35 }
36
37 async isEnabled(option: OptionCodename): Promise<boolean> {
Adrià Vilanova Martínez9326a002025-02-08 23:16:12 +010038 return (await this.getOptionsConfiguration()).isEnabled(option);
39 }
40
41 async getOptionsConfiguration(): Promise<OptionsConfiguration> {
Adrià Vilanova Martínez6ecaa0d2024-10-26 17:04:32 +020042 this.setUp();
43 return this.mutex.runExclusive(
Adrià Vilanova Martínez9326a002025-02-08 23:16:12 +010044 () => this.optionsConfiguration,
Adrià Vilanova Martínez6ecaa0d2024-10-26 17:04:32 +020045 kReadPriority,
46 );
47 }
48
49 async getOptionsValues(): Promise<OptionsValues> {
Adrià Vilanova Martínez9326a002025-02-08 23:16:12 +010050 return (await this.getOptionsConfiguration()).optionsValues;
Adrià Vilanova Martínez6ecaa0d2024-10-26 17:04:32 +020051 }
52
53 addListener(listener: OptionsChangeListener) {
54 this.setUp();
55 this.listeners.add(listener);
56 }
57
58 private setUp() {
59 if (this.isSetUp) return;
60
Adrià Vilanova Martínezc8628b12024-11-10 22:29:45 +010061 this.isSetUp = true;
Adrià Vilanova Martínezad696762024-05-25 19:32:36 +020062 this.listenForStorageChanges();
63 this.updateValues();
64 }
65
66 /**
67 * Sets up a listener to update the current cached configuration when there
68 * are changes to the underlying storage where options are persisted.
69 *
70 * We could try only doing this only when we're sure it has changed, but
71 * there are many factors (if the user has changed it manually, if a kill
72 * switch was activated, etc.) so we do it every time there is any change in
73 * the underlying storage.
74 */
75 private listenForStorageChanges() {
Adrià Vilanova Martínezf7e86852024-05-11 14:16:38 +020076 chrome.storage.onChanged.addListener((_, areaName) => {
77 if (areaName !== 'sync') return;
Adrià Vilanova Martínezad696762024-05-25 19:32:36 +020078 console.debug('[OptionsProvider] Retrieving updated options.');
79 this.updateValues();
Adrià Vilanova Martínezf7e86852024-05-11 14:16:38 +020080 });
81 }
82
Adrià Vilanova Martínezad696762024-05-25 19:32:36 +020083 private async updateValues() {
84 await this.mutex.runExclusive(async () => {
85 await this.nonSafeUpdateValues();
86 }, kWritePriority);
Adrià Vilanova Martínezf7e86852024-05-11 14:16:38 +020087 }
88
Adrià Vilanova Martínezad696762024-05-25 19:32:36 +020089 private async nonSafeUpdateValues() {
90 const previousConfiguration = this.optionsConfiguration;
91 const currentOptionsValues = await getOptions(null);
92 this.optionsConfiguration = new OptionsConfiguration(currentOptionsValues);
93
94 this.notifyListenersIfApplicable(previousConfiguration);
95 }
96
97 private async notifyListenersIfApplicable(
98 previousOptionsConfiguration: OptionsConfiguration,
99 ) {
100 if (
101 !previousOptionsConfiguration ||
102 this.optionsConfiguration.isEqualTo(previousOptionsConfiguration)
103 ) {
104 return;
105 }
106
107 for (const listener of this.listeners) {
108 listener(previousOptionsConfiguration, this.optionsConfiguration);
109 }
110 }
Adrià Vilanova Martínezf7e86852024-05-11 14:16:38 +0200111}