diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/background/Application.ts | 13 | ||||
-rw-r--r-- | src/background/controllers/SettingController.ts | 2 | ||||
-rw-r--r-- | src/background/di.ts | 11 | ||||
-rw-r--r-- | src/background/index.ts | 1 | ||||
-rw-r--r-- | src/background/presenters/Notifier.ts (renamed from src/background/presenters/NotifyPresenter.ts) | 11 | ||||
-rw-r--r-- | src/background/repositories/CachedSettingRepository.ts | 63 | ||||
-rw-r--r-- | src/background/repositories/PersistentSettingRepository.ts | 14 | ||||
-rw-r--r-- | src/background/repositories/SettingRepository.ts | 83 | ||||
-rw-r--r-- | src/background/usecases/CommandUseCase.ts | 10 | ||||
-rw-r--r-- | src/background/usecases/CompletionsUseCase.ts | 10 | ||||
-rw-r--r-- | src/background/usecases/SettingUseCase.ts | 45 | ||||
-rw-r--r-- | src/background/usecases/VersionUseCase.ts | 8 | ||||
-rw-r--r-- | src/settings/actions/setting.ts | 1 | ||||
-rw-r--r-- | src/settings/storage.ts | 25 |
14 files changed, 187 insertions, 110 deletions
diff --git a/src/background/Application.ts b/src/background/Application.ts index 08013cd..c2c48b5 100644 --- a/src/background/Application.ts +++ b/src/background/Application.ts @@ -1,7 +1,8 @@ -import { injectable } from 'tsyringe'; +import { injectable, inject } from 'tsyringe'; import ContentMessageListener from './infrastructures/ContentMessageListener'; import SettingController from './controllers/SettingController'; import VersionController from './controllers/VersionController'; +import SettingRepository from "./repositories/SettingRepository"; @injectable() export default class Application { @@ -9,6 +10,7 @@ export default class Application { private contentMessageListener: ContentMessageListener, private settingController: SettingController, private versionController: VersionController, + @inject("SyncSettingRepository") private syncSettingRepository: SettingRepository, ) { } @@ -23,13 +25,8 @@ export default class Application { }); this.contentMessageListener.run(); - browser.storage.onChanged.addListener((changes, area) => { - if (area !== 'local') { - return; - } - if (changes.settings) { - this.settingController.reload(); - } + this.syncSettingRepository.onChange(() => { + this.settingController.reload(); }); } } diff --git a/src/background/controllers/SettingController.ts b/src/background/controllers/SettingController.ts index 8d05852..26edc07 100644 --- a/src/background/controllers/SettingController.ts +++ b/src/background/controllers/SettingController.ts @@ -12,7 +12,7 @@ export default class SettingController { } getSetting(): Promise<Settings> { - return this.settingUseCase.get(); + return this.settingUseCase.getCached(); } async reload(): Promise<any> { diff --git a/src/background/di.ts b/src/background/di.ts new file mode 100644 index 0000000..9fc230c --- /dev/null +++ b/src/background/di.ts @@ -0,0 +1,11 @@ +/* eslint-disable max-len */ + +import { LocalSettingRepository, SyncSettingRepository } from "./repositories/SettingRepository"; +import { NotifierImpl } from "./presenters/Notifier"; +import { CachedSettingRepositoryImpl } from "./repositories/CachedSettingRepository"; +import { container } from 'tsyringe'; + +container.register('LocalSettingRepository', { useValue: LocalSettingRepository }); +container.register('SyncSettingRepository', { useClass: SyncSettingRepository }); +container.register('CachedSettingRepository', { useClass: CachedSettingRepositoryImpl }); +container.register('Notifier', { useClass: NotifierImpl }); diff --git a/src/background/index.ts b/src/background/index.ts index 51fde56..f75c53b 100644 --- a/src/background/index.ts +++ b/src/background/index.ts @@ -1,6 +1,7 @@ import 'reflect-metadata'; import { container } from 'tsyringe'; import Application from './Application'; +import './di'; const app = container.resolve(Application); app.run(); diff --git a/src/background/presenters/NotifyPresenter.ts b/src/background/presenters/Notifier.ts index b7f4f99..57d58cb 100644 --- a/src/background/presenters/NotifyPresenter.ts +++ b/src/background/presenters/Notifier.ts @@ -1,10 +1,13 @@ -import { injectable } from 'tsyringe'; - const NOTIFICATION_ID_UPDATE = 'vimvixen-update'; const NOTIFICATION_ID_INVALID_SETTINGS = 'vimvixen-update-invalid-settings'; -@injectable() -export default class NotifyPresenter { +export default interface Notifier { + notifyUpdated(version: string, onclick: () => void): Promise<void>; + + notifyInvalidSettings(onclick: () => void): Promise<void>; +} + +export class NotifierImpl implements NotifierImpl { async notifyUpdated(version: string, onclick: () => void): Promise<void> { const title = `Vim Vixen ${version} has been installed`; const message = 'Click here to see release notes'; diff --git a/src/background/repositories/CachedSettingRepository.ts b/src/background/repositories/CachedSettingRepository.ts new file mode 100644 index 0000000..1af15d4 --- /dev/null +++ b/src/background/repositories/CachedSettingRepository.ts @@ -0,0 +1,63 @@ +import MemoryStorage from '../infrastructures/MemoryStorage'; +import Settings from '../../shared/settings/Settings'; +import Properties from '../../shared/settings/Properties'; + +const CACHED_SETTING_KEY = 'setting'; + +export default interface CachedSettingRepository { + get(): Promise<Settings>; + + update(value: Settings): Promise<void>; + + setProperty( + name: string, value: string | number | boolean, + ): Promise<void>; +} + +export class CachedSettingRepositoryImpl implements CachedSettingRepository { + private cache: MemoryStorage; + + constructor() { + this.cache = new MemoryStorage(); + } + + get(): Promise<Settings> { + const data = this.cache.get(CACHED_SETTING_KEY); + return Promise.resolve(Settings.fromJSON(data)); + } + + update(value: Settings): Promise<void> { + this.cache.set(CACHED_SETTING_KEY, value.toJSON()); + return Promise.resolve() + } + + async setProperty( + name: string, value: string | number | boolean, + ): Promise<void> { + const def = Properties.def(name); + if (!def) { + throw new Error('unknown property: ' + name); + } + if (typeof value !== def.type) { + throw new TypeError(`property type of ${name} mismatch: ${typeof value}`); + } + let newValue = value; + if (typeof value === 'string' && value === '') { + newValue = def.defaultValue; + } + + const current = await this.get(); + switch (name) { + case 'hintchars': + current.properties.hintchars = newValue as string; + break; + case 'smoothscroll': + current.properties.smoothscroll = newValue as boolean; + break; + case 'complete': + current.properties.complete = newValue as string; + break; + } + await this.update(current); + } +} diff --git a/src/background/repositories/PersistentSettingRepository.ts b/src/background/repositories/PersistentSettingRepository.ts deleted file mode 100644 index c10f2cf..0000000 --- a/src/background/repositories/PersistentSettingRepository.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { injectable } from 'tsyringe'; -import SettingData from '../../shared/SettingData'; - -@injectable() -export default class SettingRepository { - async load(): Promise<SettingData | null> { - const { settings } = await browser.storage.local.get('settings'); - if (!settings) { - return null; - } - return SettingData.fromJSON(settings as any); - } -} - diff --git a/src/background/repositories/SettingRepository.ts b/src/background/repositories/SettingRepository.ts index ba24e36..b522045 100644 --- a/src/background/repositories/SettingRepository.ts +++ b/src/background/repositories/SettingRepository.ts @@ -1,54 +1,49 @@ -import { injectable } from 'tsyringe'; -import MemoryStorage from '../infrastructures/MemoryStorage'; -import Settings from '../../shared/settings/Settings'; -import Properties from '../../shared/settings/Properties'; +import SettingData from '../../shared/SettingData'; -const CACHED_SETTING_KEY = 'setting'; +export default interface SettingRepository { + load(): Promise<SettingData | null>; -@injectable() -export default class SettingRepository { - private cache: MemoryStorage; - - constructor() { - this.cache = new MemoryStorage(); - } + onChange(callback: () => void): void; +} - get(): Promise<Settings> { - const data = this.cache.get(CACHED_SETTING_KEY); - return Promise.resolve(Settings.fromJSON(data)); +export class LocalSettingRepository implements SettingRepository { + async load(): Promise<SettingData | null> { + const {settings} = await browser.storage.local.get('settings'); + if (!settings) { + return null; + } + return SettingData.fromJSON(settings as any); } - update(value: Settings): void { - return this.cache.set(CACHED_SETTING_KEY, value.toJSON()); + onChange(callback: () => void) { + browser.storage.onChanged.addListener((changes, area) => { + if (area !== 'local') { + return; + } + if (changes.settings) { + callback(); + } + }); } +} - async setProperty( - name: string, value: string | number | boolean, - ): Promise<void> { - const def = Properties.def(name); - if (!def) { - throw new Error('unknown property: ' + name); - } - if (typeof value !== def.type) { - throw new TypeError(`property type of ${name} mismatch: ${typeof value}`); - } - let newValue = value; - if (typeof value === 'string' && value === '') { - newValue = def.defaultValue; +export class SyncSettingRepository implements SettingRepository { + async load(): Promise<SettingData | null> { + const {settings} = await browser.storage.sync.get('settings'); + if (!settings) { + return null; } + return SettingData.fromJSON(settings as any); + } - const current = await this.get(); - switch (name) { - case 'hintchars': - current.properties.hintchars = newValue as string; - break; - case 'smoothscroll': - current.properties.smoothscroll = newValue as boolean; - break; - case 'complete': - current.properties.complete = newValue as string; - break; - } - return this.update(current); + onChange(callback: () => void) { + browser.storage.onChanged.addListener((changes, area) => { + if (area !== 'sync') { + return; + } + if (changes.settings) { + callback(); + } + }); } -} +}
\ No newline at end of file diff --git a/src/background/usecases/CommandUseCase.ts b/src/background/usecases/CommandUseCase.ts index fcb898d..7dba664 100644 --- a/src/background/usecases/CommandUseCase.ts +++ b/src/background/usecases/CommandUseCase.ts @@ -1,11 +1,11 @@ -import { injectable } from 'tsyringe'; +import { injectable, inject } from 'tsyringe'; import * as operations from '../../shared/operations'; import * as parsers from './parsers'; import * as urls from '../../shared/urls'; import TabPresenter from '../presenters/TabPresenter'; import WindowPresenter from '../presenters/WindowPresenter'; import HelpPresenter from '../presenters/HelpPresenter'; -import SettingRepository from '../repositories/SettingRepository'; +import CachedSettingRepository from '../repositories/CachedSettingRepository'; import BookmarkRepository from '../repositories/BookmarkRepository'; import ConsoleClient from '../infrastructures/ConsoleClient'; import ContentMessageClient from '../infrastructures/ContentMessageClient'; @@ -17,7 +17,7 @@ export default class CommandIndicator { private tabPresenter: TabPresenter, private windowPresenter: WindowPresenter, private helpPresenter: HelpPresenter, - private settingRepository: SettingRepository, + @inject("CachedSettingRepository") private cachedSettingRepository: CachedSettingRepository, private bookmarkRepository: BookmarkRepository, private consoleClient: ConsoleClient, private contentMessageClient: ContentMessageClient, @@ -133,7 +133,7 @@ export default class CommandIndicator { return; } const [name, value] = parsers.parseSetOption(keywords); - await this.settingRepository.setProperty(name, value); + await this.cachedSettingRepository.setProperty(name, value); return this.contentMessageClient.broadcastSettingsChanged(); } @@ -143,7 +143,7 @@ export default class CommandIndicator { } private async urlOrSearch(keywords: string): Promise<any> { - const settings = await this.settingRepository.get(); + const settings = await this.cachedSettingRepository.get(); return urls.searchUrl(keywords, settings.search); } } diff --git a/src/background/usecases/CompletionsUseCase.ts b/src/background/usecases/CompletionsUseCase.ts index 779c61d..9874644 100644 --- a/src/background/usecases/CompletionsUseCase.ts +++ b/src/background/usecases/CompletionsUseCase.ts @@ -1,9 +1,9 @@ -import { injectable } from 'tsyringe'; +import { injectable, inject } from 'tsyringe'; import CompletionGroup from '../domains/CompletionGroup'; import CommandDocs from '../domains/CommandDocs'; import CompletionsRepository from '../repositories/CompletionsRepository'; import * as filters from './filters'; -import SettingRepository from '../repositories/SettingRepository'; +import CachedSettingRepository from '../repositories/CachedSettingRepository'; import TabPresenter from '../presenters/TabPresenter'; import Properties from '../../shared/settings/Properties'; @@ -17,7 +17,7 @@ export default class CompletionsUseCase { constructor( private tabPresenter: TabPresenter, private completionsRepository: CompletionsRepository, - private settingRepository: SettingRepository, + @inject("CachedSettingRepository") private cachedSettingRepository: CachedSettingRepository, ) { } @@ -41,7 +41,7 @@ export default class CompletionsUseCase { // TODO This logic contains view entities. They should be defined on // content script - const settings = await this.settingRepository.get(); + const settings = await this.cachedSettingRepository.get(); const groups: CompletionGroup[] = []; const complete = settings.properties.complete; @@ -180,7 +180,7 @@ export default class CompletionsUseCase { } async querySearchEngineItems(name: string, keywords: string) { - const settings = await this.settingRepository.get(); + const settings = await this.cachedSettingRepository.get(); const engines = Object.keys(settings.search.engines) .filter(key => key.startsWith(keywords)); return engines.map(key => ({ diff --git a/src/background/usecases/SettingUseCase.ts b/src/background/usecases/SettingUseCase.ts index d78d440..69b4572 100644 --- a/src/background/usecases/SettingUseCase.ts +++ b/src/background/usecases/SettingUseCase.ts @@ -1,35 +1,32 @@ -import { injectable } from 'tsyringe'; -import PersistentSettingRepository - from '../repositories/PersistentSettingRepository'; -import SettingRepository from '../repositories/SettingRepository'; -import { DefaultSettingData } from '../../shared/SettingData'; +import {inject, injectable} from 'tsyringe'; +import CachedSettingRepository from '../repositories/CachedSettingRepository'; +import SettingData, {DefaultSettingData} from '../../shared/SettingData'; import Settings from '../../shared/settings/Settings'; -import NotifyPresenter from '../presenters/NotifyPresenter'; +import Notifier from '../presenters/Notifier'; +import SettingRepository from "../repositories/SettingRepository"; @injectable() export default class SettingUseCase { constructor( - private persistentSettingRepository: PersistentSettingRepository, - private settingRepository: SettingRepository, - private notifyPresenter: NotifyPresenter, + @inject("LocalSettingRepository") private localSettingRepository: SettingRepository, + @inject("SyncSettingRepository") private syncSettingRepository: SettingRepository, + @inject("CachedSettingRepository") private cachedSettingRepository: CachedSettingRepository, + @inject("Notifier") private notifier: Notifier, ) { } - get(): Promise<Settings> { - return this.settingRepository.get(); + getCached(): Promise<Settings> { + return this.cachedSettingRepository.get(); } async reload(): Promise<Settings> { - let data; + let data = DefaultSettingData; try { - data = await this.persistentSettingRepository.load(); + data = await this.loadSettings(); } catch (e) { this.showUnableToLoad(e); } - if (!data) { - data = DefaultSettingData; - } let value: Settings; try { @@ -38,13 +35,25 @@ export default class SettingUseCase { this.showUnableToLoad(e); value = DefaultSettingData.toSettings(); } - this.settingRepository.update(value!!); + await this.cachedSettingRepository.update(value!!); return value; } + private async loadSettings(): Promise<SettingData> { + const sync = await this.syncSettingRepository.load(); + if (sync) { + return sync; + } + const local = await this.localSettingRepository.load(); + if (local) { + return local; + } + return DefaultSettingData; + } + private showUnableToLoad(e: Error) { console.error('unable to load settings', e); - this.notifyPresenter.notifyInvalidSettings(() => { + this.notifier.notifyInvalidSettings(() => { browser.runtime.openOptionsPage(); }); } diff --git a/src/background/usecases/VersionUseCase.ts b/src/background/usecases/VersionUseCase.ts index 645c859..9ea8af9 100644 --- a/src/background/usecases/VersionUseCase.ts +++ b/src/background/usecases/VersionUseCase.ts @@ -1,19 +1,19 @@ -import { injectable } from 'tsyringe'; +import { injectable, inject } from 'tsyringe'; import TabPresenter from '../presenters/TabPresenter'; -import NotifyPresenter from '../presenters/NotifyPresenter'; +import Notifier from '../presenters/Notifier'; @injectable() export default class VersionUseCase { constructor( private tabPresenter: TabPresenter, - private notifyPresenter: NotifyPresenter, + @inject("Notifier") private notifier: Notifier, ) { } notify(): Promise<void> { const manifest = browser.runtime.getManifest(); const url = this.releaseNoteUrl(manifest.version); - return this.notifyPresenter.notifyUpdated(manifest.version, () => { + return this.notifier.notifyUpdated(manifest.version, () => { this.tabPresenter.create(url); }); } diff --git a/src/settings/actions/setting.ts b/src/settings/actions/setting.ts index 589ec36..93ca5f8 100644 --- a/src/settings/actions/setting.ts +++ b/src/settings/actions/setting.ts @@ -26,6 +26,7 @@ const save = async(data: SettingData): Promise<actions.SettingAction> => { return set(data); }; + const switchToForm = (json: JSONTextSettings): actions.SettingAction => { try { // toSettings exercise validation diff --git a/src/settings/storage.ts b/src/settings/storage.ts index f375e58..55cca96 100644 --- a/src/settings/storage.ts +++ b/src/settings/storage.ts @@ -1,20 +1,31 @@ import SettingData, { DefaultSettingData } from '../shared/SettingData'; -export const load = async(): Promise<SettingData> => { - const { settings } = await browser.storage.local.get('settings'); - if (!settings) { - return DefaultSettingData; +const loadSettingData = async(): Promise<SettingData> => { + const { settings: syncSettings } = await browser.storage.sync.get('settings'); + if (syncSettings) { + return SettingData.fromJSON(syncSettings as any); } + const { settings: localSettings } = await browser.storage.local.get('settings'); + if (localSettings) { + return SettingData.fromJSON(localSettings as any); + } + return DefaultSettingData +}; + +export const load = async(): Promise<SettingData> => { try { - return SettingData.fromJSON(settings as any); + return loadSettingData(); } catch (e) { console.error('unable to load settings', e); return DefaultSettingData; } }; -export const save = (data: SettingData) => { - return browser.storage.local.set({ +export const save = async (data: SettingData) => { + await browser.storage.local.set({ + settings: data.toJSON(), + }); + return browser.storage.sync.set({ settings: data.toJSON(), }); }; |