blob: 138da076e8a4af97c61b4596be593550c6f8e3ba [file] [log] [blame]
Adrià Vilanova Martínezf7e86852024-05-11 14:16:38 +02001import { Mutex, MutexInterface, withTimeout } from 'async-mutex';
2
3import { getOptions } from './optionsUtils';
Adrià Vilanova Martínezad696762024-05-25 19:32:36 +02004import { OptionCodename, OptionsValues } from './optionsPrototype';
5import { OptionsConfiguration } from './OptionsConfiguration';
Adrià Vilanova Martínezf7e86852024-05-11 14:16:38 +02006
Adrià Vilanova Martínezad696762024-05-25 19:32:36 +02007// Prioritize reads before writes.
8const kReadPriority = 10;
9const kWritePriority = 0;
10
11/**
12 * Class which provides option values and a way to listen to option changes.
13 */
Adrià Vilanova Martínezf7e86852024-05-11 14:16:38 +020014export default class OptionsProvider {
Adrià Vilanova Martínezad696762024-05-25 19:32:36 +020015 private optionsConfiguration: OptionsConfiguration;
Adrià Vilanova Martínezf7e86852024-05-11 14:16:38 +020016 private mutex: MutexInterface = withTimeout(new Mutex(), 60 * 1000);
Adrià Vilanova Martínezad696762024-05-25 19:32:36 +020017 private listeners: Set<OptionsChangeListener> = new Set();
Adrià Vilanova Martínezf7e86852024-05-11 14:16:38 +020018
19 constructor() {
Adrià Vilanova Martínezad696762024-05-25 19:32:36 +020020 this.listenForStorageChanges();
21 this.updateValues();
22 }
23
24 /**
25 * Sets up a listener to update the current cached configuration when there
26 * are changes to the underlying storage where options are persisted.
27 *
28 * We could try only doing this only when we're sure it has changed, but
29 * there are many factors (if the user has changed it manually, if a kill
30 * switch was activated, etc.) so we do it every time there is any change in
31 * the underlying storage.
32 */
33 private listenForStorageChanges() {
Adrià Vilanova Martínezf7e86852024-05-11 14:16:38 +020034 chrome.storage.onChanged.addListener((_, areaName) => {
35 if (areaName !== 'sync') return;
Adrià Vilanova Martínezad696762024-05-25 19:32:36 +020036 console.debug('[OptionsProvider] Retrieving updated options.');
37 this.updateValues();
Adrià Vilanova Martínezf7e86852024-05-11 14:16:38 +020038 });
39 }
40
Adrià Vilanova Martínezad696762024-05-25 19:32:36 +020041 private async updateValues() {
42 await this.mutex.runExclusive(async () => {
43 await this.nonSafeUpdateValues();
44 }, kWritePriority);
Adrià Vilanova Martínezf7e86852024-05-11 14:16:38 +020045 }
46
Adrià Vilanova Martínezad696762024-05-25 19:32:36 +020047 private async nonSafeUpdateValues() {
48 const previousConfiguration = this.optionsConfiguration;
49 const currentOptionsValues = await getOptions(null);
50 this.optionsConfiguration = new OptionsConfiguration(currentOptionsValues);
51
52 this.notifyListenersIfApplicable(previousConfiguration);
53 }
54
55 private async notifyListenersIfApplicable(
56 previousOptionsConfiguration: OptionsConfiguration,
57 ) {
58 if (
59 !previousOptionsConfiguration ||
60 this.optionsConfiguration.isEqualTo(previousOptionsConfiguration)
61 ) {
62 return;
63 }
64
65 for (const listener of this.listeners) {
66 listener(previousOptionsConfiguration, this.optionsConfiguration);
67 }
68 }
69
70 /**
71 * Returns the value of option |option|.
72 */
73 async getOptionValue<O extends OptionCodename>(
74 option: O,
75 ): Promise<OptionsValues[O]> {
76 return this.mutex.runExclusive(
77 () => this.optionsConfiguration.getOptionValue(option),
78 kReadPriority,
79 );
80 }
81
82 /**
83 * Returns whether |feature| is enabled.
84 */
85 async isEnabled(option: OptionCodename): Promise<boolean> {
86 return this.mutex.runExclusive(
87 () => this.optionsConfiguration.isEnabled(option),
88 kReadPriority,
89 );
90 }
91
92 async getOptionsValues(): Promise<OptionsValues> {
93 return this.mutex.runExclusive(
94 () => this.optionsConfiguration.optionsValues,
95 kReadPriority,
96 );
97 }
98
99 /**
100 * Adds a listener for changes in the options configuration.
101 */
102 addListener(listener: OptionsChangeListener) {
103 this.listeners.add(listener);
Adrià Vilanova Martínezf7e86852024-05-11 14:16:38 +0200104 }
105}
Adrià Vilanova Martínezad696762024-05-25 19:32:36 +0200106
107export type OptionsChangeListener = (
108 previousOptionValues: OptionsConfiguration,
109 currentOptionValues: OptionsConfiguration,
110) => void;