diff options
Diffstat (limited to 'ext/bg/js/options.js')
-rw-r--r-- | ext/bg/js/options.js | 80 |
1 files changed, 55 insertions, 25 deletions
diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index e61e1c48..d3220194 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -15,8 +15,21 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +/* global + * JsonSchemaValidator + */ + class OptionsUtil { - static async update(options) { + constructor() { + this._schemaValidator = new JsonSchemaValidator(); + this._optionsSchema = null; + } + + async prepare() { + this._optionsSchema = await this._fetchAsset('/bg/data/options-schema.json', true); + } + + async update(options) { // Invalid options if (!isObject(options)) { options = {}; @@ -72,14 +85,14 @@ class OptionsUtil { return await this._applyUpdates(options, this._getVersionUpdates()); } - static async load() { - let options = null; + async load() { + let options; try { const optionsStr = await new Promise((resolve, reject) => { chrome.storage.local.get(['options'], (store) => { const error = chrome.runtime.lastError; if (error) { - reject(new Error(error)); + reject(new Error(error.message)); } else { resolve(store.options); } @@ -90,15 +103,21 @@ class OptionsUtil { // NOP } - return await this.update(options); + if (typeof options !== 'undefined') { + options = await this.update(options); + } + + options = this._schemaValidator.getValidValueOrDefault(this._optionsSchema, options); + + return options; } - static save(options) { + save(options) { return new Promise((resolve, reject) => { chrome.storage.local.set({options: JSON.stringify(options)}, () => { const error = chrome.runtime.lastError; if (error) { - reject(new Error(error)); + reject(new Error(error.message)); } else { resolve(); } @@ -106,13 +125,21 @@ class OptionsUtil { }); } - static async getDefault() { - return await this.update({}); + getDefault() { + return this._schemaValidator.getValidValueOrDefault(this._optionsSchema); + } + + createValidatingProxy(options) { + return this._schemaValidator.createProxy(options, this._optionsSchema); + } + + validate(options) { + return this._schemaValidator.validate(options, this._optionsSchema); } // Legacy profile updating - static _legacyProfileUpdateGetUpdates() { + _legacyProfileUpdateGetUpdates() { return [ null, null, @@ -203,7 +230,7 @@ class OptionsUtil { ]; } - static _legacyProfileUpdateGetDefaults() { + _legacyProfileUpdateGetDefaults() { return { general: { enable: true, @@ -302,7 +329,7 @@ class OptionsUtil { }; } - static _legacyProfileUpdateAssignDefaults(options) { + _legacyProfileUpdateAssignDefaults(options) { const defaults = this._legacyProfileUpdateGetDefaults(); const combine = (target, source) => { @@ -323,7 +350,7 @@ class OptionsUtil { return options; } - static _legacyProfileUpdateUpdateVersion(options) { + _legacyProfileUpdateUpdateVersion(options) { const updates = this._legacyProfileUpdateGetUpdates(); this._legacyProfileUpdateAssignDefaults(options); @@ -345,20 +372,20 @@ class OptionsUtil { // Private - static async _addFieldTemplatesToOptions(options, additionSourceUrl) { + async _addFieldTemplatesToOptions(options, additionSourceUrl) { let addition = null; for (const {options: profileOptions} of options.profiles) { const fieldTemplates = profileOptions.anki.fieldTemplates; if (fieldTemplates !== null) { if (addition === null) { - addition = await this._readFile(additionSourceUrl); + addition = await this._fetchAsset(additionSourceUrl); } profileOptions.anki.fieldTemplates = this._addFieldTemplatesBeforeEnd(fieldTemplates, addition); } } } - static async _addFieldTemplatesBeforeEnd(fieldTemplates, addition) { + async _addFieldTemplatesBeforeEnd(fieldTemplates, addition) { const pattern = /[ \t]*\{\{~?>\s*\(\s*lookup\s*\.\s*"marker"\s*\)\s*~?\}\}/; const newline = '\n'; let replaced = false; @@ -373,7 +400,7 @@ class OptionsUtil { return fieldTemplates; } - static async _readFile(url) { + async _fetchAsset(url, json=false) { url = chrome.runtime.getURL(url); const response = await fetch(url, { method: 'GET', @@ -383,10 +410,13 @@ class OptionsUtil { redirect: 'follow', referrerPolicy: 'no-referrer' }); - return await response.text(); + if (!response.ok) { + throw new Error(`Failed to fetch ${url}: ${response.status}`); + } + return await (json ? response.json() : response.text()); } - static _getStringHashCode(string) { + _getStringHashCode(string) { let hashCode = 0; if (typeof string !== 'string') { return hashCode; } @@ -399,7 +429,7 @@ class OptionsUtil { return hashCode; } - static async _applyUpdates(options, updates) { + async _applyUpdates(options, updates) { const targetVersion = updates.length; let currentVersion = options.version; @@ -417,7 +447,7 @@ class OptionsUtil { return options; } - static _getVersionUpdates() { + _getVersionUpdates() { return [ { async: false, @@ -438,7 +468,7 @@ class OptionsUtil { ]; } - static _updateVersion1(options) { + _updateVersion1(options) { // Version 1 changes: // Added options.global.database.prefixWildcardsSupported = false. options.global = { @@ -449,7 +479,7 @@ class OptionsUtil { return options; } - static _updateVersion2(options) { + _updateVersion2(options) { // Version 2 changes: // Legacy profile update process moved into this upgrade function. for (const profile of options.profiles) { @@ -461,14 +491,14 @@ class OptionsUtil { return options; } - static async _updateVersion3(options) { + async _updateVersion3(options) { // Version 3 changes: // Pitch accent Anki field templates added. await this._addFieldTemplatesToOptions(options, '/bg/data/anki-field-templates-upgrade-v2.handlebars'); return options; } - static async _updateVersion4(options) { + async _updateVersion4(options) { // Version 4 changes: // Options conditions converted to string representations. // Added usePopupWindow. |