aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/background/Application.ts13
-rw-r--r--src/background/controllers/SettingController.ts2
-rw-r--r--src/background/di.ts11
-rw-r--r--src/background/index.ts1
-rw-r--r--src/background/presenters/Notifier.ts (renamed from src/background/presenters/NotifyPresenter.ts)11
-rw-r--r--src/background/repositories/CachedSettingRepository.ts63
-rw-r--r--src/background/repositories/PersistentSettingRepository.ts14
-rw-r--r--src/background/repositories/SettingRepository.ts83
-rw-r--r--src/background/usecases/CommandUseCase.ts10
-rw-r--r--src/background/usecases/CompletionsUseCase.ts10
-rw-r--r--src/background/usecases/SettingUseCase.ts45
-rw-r--r--src/background/usecases/VersionUseCase.ts8
-rw-r--r--src/settings/actions/setting.ts1
-rw-r--r--src/settings/storage.ts25
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(),
});
};