From 4b9a4bdee365f431241c716f5168576a8df80621 Mon Sep 17 00:00:00 2001 From: Edmundas Ramanauskas Date: Fri, 19 Apr 2024 15:19:30 +0300 Subject: [PATCH] Simplify state and storage --- package.json | 4 +- pnpm-lock.yaml | 16 ------ src/popup/context.ts | 10 ++-- src/popup/main.ts | 7 ++- src/state.ts | 119 ------------------------------------------- src/store.ts | 56 +++++++++++--------- src/worker.ts | 27 ++-------- 7 files changed, 48 insertions(+), 191 deletions(-) delete mode 100644 src/state.ts diff --git a/package.json b/package.json index a5e85c5..28adf2e 100644 --- a/package.json +++ b/package.json @@ -38,11 +38,9 @@ "yargs": "^17.7.2" }, "dependencies": { - "dexie": "^4.0.4", "emittery": "^1.0.3", "loglevel": "^1.9.1", "uuid": "^9.0.1", - "webextension-polyfill": "^0.11.0", - "xstate": "^5.10.0" + "webextension-polyfill": "^0.11.0" } } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a30c1c7..6f98a08 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,6 @@ importers: .: dependencies: - dexie: - specifier: ^4.0.4 - version: 4.0.4 emittery: specifier: ^1.0.3 version: 1.0.3 @@ -23,9 +20,6 @@ importers: webextension-polyfill: specifier: ^0.11.0 version: 0.11.0(esbuild@0.20.2) - xstate: - specifier: ^5.10.0 - version: 5.11.0 devDependencies: '@eslint/js': specifier: ^9.0.0 @@ -630,9 +624,6 @@ packages: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} engines: {node: '>=8'} - dexie@4.0.4: - resolution: {integrity: sha512-wFzwWSUdi+MC3jiFeQcCp9nInR7EaX8edzYY+4wmiITkQAiSnHpe4Wo2o5Ce5tJZe2nqt7mLW91MsW4GYx3ziQ==} - dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -1521,9 +1512,6 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - xstate@5.11.0: - resolution: {integrity: sha512-0MqTLpc7dr/hXFHY25oN4sdnO3Ey6MYy9WkWxOgiwjPV0S6rWwLb5nZlRlPDSku2GEV4/y6AR8bX+GNCOxnEwA==} - y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -2074,8 +2062,6 @@ snapshots: detect-indent@6.1.0: {} - dexie@4.0.4: {} - dir-glob@3.0.1: dependencies: path-type: 4.0.0 @@ -2973,8 +2959,6 @@ snapshots: wrappy@1.0.2: {} - xstate@5.11.0: {} - y18n@5.0.8: {} yallist@4.0.0: {} diff --git a/src/popup/context.ts b/src/popup/context.ts index ad51d99..4a5c17c 100644 --- a/src/popup/context.ts +++ b/src/popup/context.ts @@ -2,8 +2,7 @@ import { getContext as getCtx } from 'svelte'; import { type Readable, readable } from 'svelte/store'; import Connection from '../comms/child'; -import type { UrlData } from '../state'; -import StateDatabase from '../store'; +import type { UrlData, Store } from '../store'; /** * context object for svelte app @@ -14,11 +13,10 @@ export type Context = { urls: Readable; }; -export function makeContext(name: string) { - const db = new StateDatabase(); +export function makeContext(name: string, store: Store) { const port = new Connection(name); - const urls = readable([], (set) => { - db.subscribe('urls', (data) => { + const urls = readable(store.urls, (set) => { + store.subscribe('urls', (data) => { set(data); }); }); diff --git a/src/popup/main.ts b/src/popup/main.ts index ccf011b..1c3cd1f 100644 --- a/src/popup/main.ts +++ b/src/popup/main.ts @@ -1,12 +1,15 @@ import '../css/reset.css'; +import { Store } from '../store'; import App from './App.svelte'; import { makeContext } from './context'; start(); -function start() { - const context = makeContext('popup'); +async function start() { + const store = new Store(); + await store.init(); + const context = makeContext('popup', store); new App({ context, diff --git a/src/state.ts b/src/state.ts deleted file mode 100644 index d06994d..0000000 --- a/src/state.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { assign, createActor, setup } from 'xstate'; - -export type UrlData = { - id: string; - url: string; -}; - -type Context = { - urls: UrlData[]; -}; - -type InsertUrlEvent = { - type: 'INSERT_URL'; - id: string; - url: string; -}; -type RemoveUrlEvent = { - type: 'REMOVE_URL'; - id: string; -}; -type UpdateUrlEvent = { - type: 'UPDATE_URL'; - id: string; - url: string; -}; -type RestoreStateEvent = { - type: 'RESTORE_STATE'; - urls: UrlData[]; -}; - -type Event = - | InsertUrlEvent - | RemoveUrlEvent - | UpdateUrlEvent - | RestoreStateEvent - | { type: 'PAUSE' } - | { type: 'START' }; - -function createMachine() { - return setup({ - types: { - context: {} as Context, - events: {} as Event, - }, - }).createMachine({ - initial: 'disabled', - context: { - urls: [], - }, - states: { - enabled: { - on: { - INSERT_URL: { - actions: assign(({ context, event }) => { - const { id, url } = event; - const { urls } = context; - return { - ...context, - urls: urls.concat({ id, url }), - }; - }), - guard: ({ context, event }) => { - const { url } = event; - const { urls } = context; - return !!url && !urls.some((data) => data.url === url); - }, - }, - REMOVE_URL: { - actions: assign(({ context, event }) => { - const { id } = event; - const { urls } = context; - return { - ...context, - urls: urls.filter((data) => data.id !== id), - }; - }), - }, - UPDATE_URL: { - actions: assign(({ context, event }) => { - const { id, url } = event; - const { urls } = context; - return { - ...context, - urls: urls.map((data) => { - if (data.id === id) { - return { - ...data, - url, - }; - } - return data; - }), - }; - }), - }, - PAUSE: { target: 'disabled' }, - }, - }, - disabled: { - on: { - RESTORE_STATE: { - actions: assign(({ context, event }) => { - const { urls } = event; - return { ...context, urls }; - }), - }, - START: { target: 'enabled' }, - }, - }, - }, - }); -} - -export default function createStateMachine() { - const machine = createMachine(); - const service = createActor(machine); - service.start(); - return service; -} diff --git a/src/store.ts b/src/store.ts index 508ec7a..7bf9081 100644 --- a/src/store.ts +++ b/src/store.ts @@ -1,33 +1,43 @@ -import Dexie, { liveQuery, type PromiseExtended, type Subscription, type Table } from 'dexie'; +import { v4 as uuid } from 'uuid'; +import browser from 'webextension-polyfill'; -import type { UrlData } from './state'; +export type UrlData = { + id: string; + url: string; +}; -// -// Declare Database -// -export default class StateDatabase extends Dexie { - public urls!: Table; +export class Store { + private _urls: { id: string; url: string }[] = []; - public constructor() { - super('StateDatabase'); - this.version(2).stores({ - urls: 'id,url', - }); + constructor(private engine = browser.storage.local) {} + + get urls() { + return this._urls; } - public subscribe(tableName: 'urls', callback: (values: T[]) => void): Subscription; - public subscribe(tableName: 'urls', callback: (values: T) => void, id: string): Subscription; - public subscribe(tableName: 'urls', callback: (values: T | T[]) => void, id?: string) { - const table = this[tableName]; + public async init() { + this._urls = await this.getItem<{ id: string; url: string }[]>('urls', this._urls); + } - if (id) { - return liveQuery(() => table.get({ [table.schema.primKey.name]: id }) as PromiseExtended).subscribe({ - next: callback, - }); - } + public async addUrl(url: string) { + this._urls.push({ id: uuid(), url }); + await this.persistUrls(); + } - return liveQuery(() => table.toArray() as PromiseExtended).subscribe({ - next: callback, + public subscribe(key: 'urls', callback: (value: T) => void) { + this.engine.onChanged.addListener((changes) => { + if (key in changes) { + callback(changes[key].newValue); + } }); } + + private async persistUrls() { + await this.engine.set({ urls: this._urls }); + } + + private async getItem(key: string, value: T) { + const store = await this.engine.get({ [key]: value }); + return store[key] as T; + } } diff --git a/src/worker.ts b/src/worker.ts index 19f1eba..da6f60a 100644 --- a/src/worker.ts +++ b/src/worker.ts @@ -1,26 +1,16 @@ -import { v4 as uuid } from 'uuid'; import browser from 'webextension-polyfill'; import Connection from './comms/main'; import { addEventListener, type DomContentLoadedData } from './events'; import { injectScript } from './libs/utils'; import logger from './logger'; -import createStateMachine from './state'; -import StateDatabase from './store'; +import { Store } from './store'; start(); async function start() { - const db = new StateDatabase(); - const state = createStateMachine(); - await restoreState(db, state); - - // persist state changes - state.subscribe(async ({ context }) => { - const { urls } = context; - await db.urls.clear(); - await db.urls.bulkPut(urls); - }); + const store = new Store(); + await store.init(); // listen for messages from popup const popup = new Connection('popup'); @@ -37,8 +27,8 @@ async function start() { }); // listen for store_url messages - widget.addListener(({ data }) => { - state.send({ type: 'INSERT_URL', id: uuid(), url: data }); + widget.addListener(async ({ data }) => { + await store.addUrl(data); }, 'store_url'); // inject content script into third party pages @@ -55,11 +45,4 @@ async function start() { await injectScript(tabId, 'content/main.js'); logger.debug('CONTENT_SCRIPT_INJECTED', tabId, url); }); - - state.send({ type: 'START' }); -} - -async function restoreState(db: StateDatabase, state: ReturnType) { - const urls = await db.urls.toArray(); - state.send({ type: 'RESTORE_STATE', urls }); }