From d8b20825caaa0dd01a142b00c7109a51a8073f5e Mon Sep 17 00:00:00 2001 From: skid-dev <62094231+skid-dev@users.noreply.github.com> Date: Sun, 17 May 2026 15:13:28 +1000 Subject: [PATCH] refactor: migrate storage wrappers to WXT typed items Replace raw chrome.storage.sync/local calls in src/storage/* and src/background/set_setting.ts with WXT's storage.defineItem and storage.getItem/setItem helpers. Storage areas are preserved per key (sync for settings, local for news channels / recents / revisions). - Add src/storage/items.ts: typed singletons (settings_item, recents_item) and small wrappers (get_/set_news_channel_items, get_/set_revision_entry, list_news_channel_names) over storage.getItem/setItem for the dynamic per-channel and per-revision keys. - Rewrite get.ts / set.ts / viewed_pages.ts / revisions/* against those helpers. Public function signatures unchanged so existing callers in background, content scripts, and entrypoints keep working. - Update set_default_settings.ts + set_setting.ts to read/write the Settings object through settings_item instead of poking storage directly. - Drop revisions/common.ts (the legacy news_recent_ prefix now lives in items.ts) and the unused ensure_recents_exists() helper that the fallback: [] default makes unnecessary. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/background/set_default_settings.ts | 7 +- src/background/set_setting.ts | 12 +- src/storage/get.ts | 44 +++--- src/storage/items.ts | 55 ++++++++ src/storage/revisions/common.ts | 6 - src/storage/revisions/revision_get.ts | 6 +- src/storage/revisions/revision_set.ts | 15 +-- src/storage/set.ts | 180 ++++++++++--------------- src/storage/viewed_pages.ts | 29 +--- 9 files changed, 166 insertions(+), 188 deletions(-) create mode 100644 src/storage/items.ts delete mode 100644 src/storage/revisions/common.ts diff --git a/src/background/set_default_settings.ts b/src/background/set_default_settings.ts index f88a9f7..2746fc8 100644 --- a/src/background/set_default_settings.ts +++ b/src/background/set_default_settings.ts @@ -1,4 +1,5 @@ import { Settings } from "../types/settings" +import { settings_item } from "../storage/items" import { poll_feed } from "./pull_feed" // set_setting was moved to ./set_setting so content-script entrypoints @@ -6,9 +7,9 @@ import { poll_feed } from "./pull_feed" export { set_setting } from "./set_setting" export async function init_settings(): Promise { - let current_settings = await chrome.storage.sync.get("settings") + const current_settings = await settings_item.getValue() - if (current_settings.settings) { + if (current_settings) { // settings already exist, no need to set defaults await poll_feed() return @@ -32,7 +33,7 @@ export async function init_settings(): Promise { record_post_history: true, record_setting_active: false, } - await chrome.storage.sync.set({ settings: initial_settings }) + await settings_item.setValue(initial_settings) await poll_feed() } diff --git a/src/background/set_setting.ts b/src/background/set_setting.ts index 453f1c8..b150e7e 100644 --- a/src/background/set_setting.ts +++ b/src/background/set_setting.ts @@ -1,18 +1,18 @@ import { Settings } from "../types/settings" +import { settings_item } from "../storage/items" export async function set_setting( key: K, value: Settings[K] ): Promise { - const current_settings = await chrome.storage.sync.get("settings") - if (!current_settings.settings) { + const current_settings = await settings_item.getValue() + if (!current_settings) { console.error("Settings not initialized yet.") return } - const new_settings = { - ...current_settings.settings, + await settings_item.setValue({ + ...current_settings, [key]: value, - } - await chrome.storage.sync.set({ settings: new_settings }) + }) } diff --git a/src/storage/get.ts b/src/storage/get.ts index 560beef..91c45ae 100644 --- a/src/storage/get.ts +++ b/src/storage/get.ts @@ -1,28 +1,20 @@ import { ItemRecord } from "../types/item_record" +import { get_news_channel_items, list_news_channel_names } from "./items" export async function get_all_news_channels(): Promise { - const news_channels = await chrome.storage.local.getKeys() - return news_channels - .filter(key => key.startsWith("news_channel_")) - .map(key => key.replace("news_channel_", "")) + return await list_news_channel_names() } export async function get_news_channel(channel_name: string): Promise { - const news_channel = await chrome.storage.local.get(`news_channel_${channel_name}`) - return news_channel[`news_channel_${channel_name}`] as ItemRecord[] || [] + return (await get_news_channel_items(channel_name)) ?? [] } export async function get_news_item( channel_name: string, item_guid: string ): Promise { - let channel_items = await get_news_channel(channel_name) - if (!channel_items) return undefined - - let this_item = channel_items.find(item => item.guid === item_guid) - if (!this_item) return undefined - - return this_item + const channel_items = await get_news_channel(channel_name) + return channel_items.find(item => item.guid === item_guid) } export interface itemWithContext { @@ -30,20 +22,18 @@ export interface itemWithContext { parent_channel: string } -export async function search_for_news_item(filter_function: (item: ItemRecord) => boolean): Promise { - let all_channels = await get_all_news_channels() - let matching_items: itemWithContext[] = [] - - for (let channel of all_channels) { - let items = await get_news_channel(channel) - let filtered_items = items.filter(filter_function) - let filtered_items_with_channel = filtered_items.map(i => { - return { - item: i, - parent_channel: channel, - } - }) - matching_items = matching_items.concat(filtered_items_with_channel) +export async function search_for_news_item( + filter_function: (item: ItemRecord) => boolean +): Promise { + const all_channels = await get_all_news_channels() + const matching_items: itemWithContext[] = [] + + for (const channel of all_channels) { + const items = await get_news_channel(channel) + const filtered = items.filter(filter_function) + for (const item of filtered) { + matching_items.push({ item, parent_channel: channel }) + } } return matching_items diff --git a/src/storage/items.ts b/src/storage/items.ts new file mode 100644 index 0000000..2201599 --- /dev/null +++ b/src/storage/items.ts @@ -0,0 +1,55 @@ +import { ItemRecord } from "../types/item_record" +import { RecentsEntry } from "../types/recents" +import { RevisionHistoryEntry } from "../types/rev_history" +import { Settings } from "../types/settings" + +// Typed storage singletons. Storage area prefix matches the area the legacy +// chrome.storage.* calls used: sync for user-editable settings, local for the +// per-channel item caches and the revision history. + +export const settings_item = storage.defineItem("sync:settings", { + fallback: null, +}) + +export const recents_item = storage.defineItem("local:recent_pages", { + fallback: [], +}) + +// Dynamic per-channel keys. defineItem can't be used at the module level for +// these since the channel name is only known at runtime, so wrap getItem / +// setItem in a small factory that captures the prefixed key and typed payload. + +const NEWS_CHANNEL_PREFIX = "news_channel_" +const REVISION_PREFIX = "news_recent_" + +export async function get_news_channel_items(channel_name: string): Promise { + return await storage.getItem(`local:${NEWS_CHANNEL_PREFIX}${channel_name}`) +} + +export async function set_news_channel_items( + channel_name: string, + items: ItemRecord[] +): Promise { + await storage.setItem(`local:${NEWS_CHANNEL_PREFIX}${channel_name}`, items) +} + +export async function get_revision_entry(rev_id: string): Promise { + return await storage.getItem(`local:${REVISION_PREFIX}${rev_id}`) +} + +export async function set_revision_entry( + rev_id: string, + entry: RevisionHistoryEntry +): Promise { + await storage.setItem(`local:${REVISION_PREFIX}${rev_id}`, entry) +} + +// Enumerate all news channel names currently in local storage. WXT's helper +// exposes `snapshot(area)` which loads every value in the area; +// chrome.storage.local.getKeys() is lighter (keys-only) so we keep it here. +export async function list_news_channel_names(): Promise { + const keys = await chrome.storage.local.getKeys() + return keys + .filter(key => key.startsWith(NEWS_CHANNEL_PREFIX)) + .map(key => key.slice(NEWS_CHANNEL_PREFIX.length)) +} diff --git a/src/storage/revisions/common.ts b/src/storage/revisions/common.ts deleted file mode 100644 index 30d727c..0000000 --- a/src/storage/revisions/common.ts +++ /dev/null @@ -1,6 +0,0 @@ -const recents_prefix = "news_recent_" - -export function get_storage_key(name: string): string { - return `${recents_prefix}${name}` -} - \ No newline at end of file diff --git a/src/storage/revisions/revision_get.ts b/src/storage/revisions/revision_get.ts index 3d80fd1..89de1b2 100644 --- a/src/storage/revisions/revision_get.ts +++ b/src/storage/revisions/revision_get.ts @@ -1,8 +1,6 @@ import { RevisionHistoryEntry } from "../../types/rev_history" -import { get_storage_key } from "./common" +import { get_revision_entry } from "../items" export async function get_revision(rev_id: string): Promise { - const item_name = get_storage_key(rev_id) - const result = await chrome.storage.local.get([item_name]) - return result[item_name] as RevisionHistoryEntry || null + return await get_revision_entry(rev_id) } diff --git a/src/storage/revisions/revision_set.ts b/src/storage/revisions/revision_set.ts index f8cebb5..1504d2d 100644 --- a/src/storage/revisions/revision_set.ts +++ b/src/storage/revisions/revision_set.ts @@ -1,5 +1,5 @@ import { RevisionData, RevisionHistoryEntry } from "../../types/rev_history" -import { get_storage_key } from "./common" +import { get_revision_entry, set_revision_entry } from "../items" import * as get_storage from "../get" import * as set_storage from "../set" @@ -12,7 +12,6 @@ export async function add_revision_to_history( diff_delete: number ): Promise { const uuid = crypto.randomUUID() - const item_name = get_storage_key(uuid) const data_object: RevisionHistoryEntry = { rev_id: uuid, @@ -22,9 +21,9 @@ export async function add_revision_to_history( diff_new_size: diff_add, diff_modified_size: diff_modify, - diff_delete_size: diff_delete + diff_delete_size: diff_delete, } - await chrome.storage.local.set({ [item_name]: data_object }) + await set_revision_entry(uuid, data_object) return data_object } @@ -37,17 +36,15 @@ export async function remove_null_revisions(item_guid: string): Promise const filtered_uuids: string[] = [] for (const uuid of item.rev_history_uuids ?? []) { - const rev_key = get_storage_key(uuid) - const rev_data = await chrome.storage.local.get(rev_key) - - if (rev_data[rev_key] !== null && rev_data[rev_key] !== undefined) { + const rev_data = await get_revision_entry(uuid) + if (rev_data) { filtered_uuids.push(uuid) } } item.rev_history_uuids = filtered_uuids await set_storage.update_item_properties("news", item_guid, { - rev_history_uuids: item.rev_history_uuids + rev_history_uuids: item.rev_history_uuids, }) return true } diff --git a/src/storage/set.ts b/src/storage/set.ts index 67d74e1..c843ceb 100644 --- a/src/storage/set.ts +++ b/src/storage/set.ts @@ -1,47 +1,39 @@ import { ItemRecord } from "../types/item_record" +import { get_news_channel_items, set_news_channel_items } from "./items" export async function add_channel(channel_name: string): Promise { - const channel_key = `news_channel_${channel_name}` - await chrome.storage.local.set({ [channel_key]: [] }) + await set_news_channel_items(channel_name, []) } export async function ensure_channel_exists(channel_name: string): Promise { - const channel_key = `news_channel_${channel_name}` - const channel_data = await chrome.storage.local.get(channel_key) - - // Check if the channel already exists - if (!channel_data[channel_key]) { + const channel_items = await get_news_channel_items(channel_name) + if (!channel_items) { await add_channel(channel_name) } } export async function clear_channel(channel_name: string): Promise { - const channel_key = `news_channel_${channel_name}` - await chrome.storage.local.set({ [channel_key]: [] }) + await set_news_channel_items(channel_name, []) } -export async function add_item_to_channel(channel_name: string, item: ItemRecord): Promise { - const channel_key = `news_channel_${channel_name}` - const channel_data = await chrome.storage.local.get(channel_key) - - // Check if the channel exists - if (!channel_data[channel_key]) { +export async function add_item_to_channel( + channel_name: string, + item: ItemRecord +): Promise { + const channel_items = await get_news_channel_items(channel_name) + if (!channel_items) { console.error(`Channel ${channel_name} does not exist.`) return false } - const channel_items: ItemRecord[] = channel_data[channel_key] as ItemRecord[] || [] - // Check if the item already exists in the channel - const item_exists = channel_items.some(existing_item => existing_item.guid === item.guid) - if (item_exists) { + if (channel_items.some(existing_item => existing_item.guid === item.guid)) { console.error(`Item with GUID ${item.guid} already exists in channel ${channel_name}.`) - return false // Item already exists, do not add + return false } - // Add the new item to the channel channel_items.push(item) - await chrome.storage.local.set({ [channel_key]: channel_items }) + await set_news_channel_items(channel_name, channel_items) return true } @@ -50,120 +42,92 @@ export async function update_item_properties( item_guid: string, new_properties: Partial ): Promise { - const channel_key = `news_channel_${channel_name}` - const channel_data = await chrome.storage.local.get(channel_key) - - // Check if the channel exists - if (!channel_data[channel_key]) { + const channel_items = await get_news_channel_items(channel_name) + if (!channel_items) { console.error(`Channel ${channel_name} does not exist.`) return } - let channel_items: ItemRecord[] = channel_data[channel_key] as ItemRecord[] || [] - - // Update the item properties - channel_items = channel_items.map(item => { - if (item.guid === item_guid) { - return { ...item, ...new_properties } - } - return item - }) + const updated = channel_items.map(item => + item.guid === item_guid ? { ...item, ...new_properties } : item + ) - await chrome.storage.local.set({ [channel_key]: channel_items }) + await set_news_channel_items(channel_name, updated) } // update item view count and last viewed time -export async function update_item_vc_and_lvt(channel_name: string, item_guid: string): Promise { - const channel_key = `news_channel_${channel_name}` - const channel_data = await chrome.storage.local.get(channel_key) - - // Check if the channel exists - if (!channel_data[channel_key]) { +export async function update_item_vc_and_lvt( + channel_name: string, + item_guid: string +): Promise { + const channel_items = await get_news_channel_items(channel_name) + if (!channel_items) { console.error(`Channel ${channel_name} does not exist.`) return null } - let channel_items: ItemRecord[] = channel_data[channel_key] as ItemRecord[] || [] - - // Increment the view count for the specified item let updated_view_count: number | null = null - channel_items = channel_items.map(item => { - if (item.guid === item_guid) { - const new_view_count = (item.view_count || 0) + 1 - updated_view_count = new_view_count - return { ...item, view_count: new_view_count, last_viewed: Date.now() } - } - return item + const updated = channel_items.map(item => { + if (item.guid !== item_guid) return item + const new_view_count = (item.view_count || 0) + 1 + updated_view_count = new_view_count + return { ...item, view_count: new_view_count, last_viewed: Date.now() } }) - await chrome.storage.local.set({ [channel_key]: channel_items }) + await set_news_channel_items(channel_name, updated) return updated_view_count } -export async function increment_item_bounce_count(channel_name: string, item_guid: string): Promise { - const channel_key = `news_channel_${channel_name}` - const channel_data = await chrome.storage.local.get(channel_key) - - // Check if the channel exists - if (!channel_data[channel_key]) { +export async function increment_item_bounce_count( + channel_name: string, + item_guid: string +): Promise { + const channel_items = await get_news_channel_items(channel_name) + if (!channel_items) { console.error(`Channel ${channel_name} does not exist.`) return } - let channel_items: ItemRecord[] = channel_data[channel_key] as ItemRecord[] || [] - - // Increment the bounce count for the specified item - channel_items = channel_items.map(item => { - if (item.guid === item_guid) { - const new_bounce_count = (item.bounce_count || 0) + 1 - return { ...item, bounce_count: new_bounce_count } - } - return item + const updated = channel_items.map(item => { + if (item.guid !== item_guid) return item + return { ...item, bounce_count: (item.bounce_count || 0) + 1 } }) - await chrome.storage.local.set({ [channel_key]: channel_items }) + await set_news_channel_items(channel_name, updated) } -export async function add_revision_history_entry(channel_name: string, item_guid: string, rev_id: string): Promise { - const channel_key = `news_channel_${channel_name}` - const channel_data = await chrome.storage.local.get(channel_key) - - // Check if the channel exists - if (!channel_data[channel_key]) { +export async function add_revision_history_entry( + channel_name: string, + item_guid: string, + rev_id: string +): Promise { + const channel_items = await get_news_channel_items(channel_name) + if (!channel_items) { console.error(`Channel ${channel_name} does not exist.`) return [] } - let channel_items: ItemRecord[] = channel_data[channel_key] as ItemRecord[] || [] - - // Add the revision ID to the item's revision history let updated_rev_history: string[] = [] - channel_items = channel_items.map(item => { - if (item.guid === item_guid) { - const existing_history = item.rev_history_uuids || [] - const new_history = [...existing_history, rev_id] - updated_rev_history = new_history - return { ...item, rev_history_uuids: new_history } - } - return item + const updated = channel_items.map(item => { + if (item.guid !== item_guid) return item + const new_history = [...(item.rev_history_uuids || []), rev_id] + updated_rev_history = new_history + return { ...item, rev_history_uuids: new_history } }) - await chrome.storage.local.set({ [channel_key]: channel_items }) + await set_news_channel_items(channel_name, updated) return updated_rev_history } -export async function remove_item_from_channel(channel_name: string, item_guid: string): Promise { - const channel_key = `news_channel_${channel_name}` - const channel_data = await chrome.storage.local.get(channel_key) - - // Check if the channel exists - if (!channel_data[channel_key]) { +export async function remove_item_from_channel( + channel_name: string, + item_guid: string +): Promise { + const channel_items = await get_news_channel_items(channel_name) + if (!channel_items) { console.error(`Channel ${channel_name} does not exist.`) return } - let channel_items: ItemRecord[] = channel_data[channel_key] as ItemRecord[] || [] - - // Remove the item from the channel - channel_items = channel_items.filter(item => item.guid !== item_guid) - await chrome.storage.local.set({ [channel_key]: channel_items }) + const updated = channel_items.filter(item => item.guid !== item_guid) + await set_news_channel_items(channel_name, updated) } export async function add_if_not_exists( @@ -171,23 +135,17 @@ export async function add_if_not_exists( item_guid: string, item: ItemRecord ): Promise { - const channel_key = `news_channel_${channel_name}` - const channel_data = await chrome.storage.local.get(channel_key) - - // Check if the channel exists - if (!channel_data[channel_key]) { + const channel_items = await get_news_channel_items(channel_name) + if (!channel_items) { console.error(`Channel ${channel_name} does not exist.`) return false } - let channel_items: ItemRecord[] = channel_data[channel_key] as ItemRecord[] || [] - - // Check if the item already exists in the channel - const item_exists = channel_items.some(existing_item => existing_item.guid === item_guid) - if (item_exists) return false // Item already exists, do not add + if (channel_items.some(existing_item => existing_item.guid === item_guid)) { + return false + } - // Add the new item to the channel channel_items.push(item) - await chrome.storage.local.set({ [channel_key]: channel_items }) + await set_news_channel_items(channel_name, channel_items) return true } diff --git a/src/storage/viewed_pages.ts b/src/storage/viewed_pages.ts index 8c06855..7d16a69 100644 --- a/src/storage/viewed_pages.ts +++ b/src/storage/viewed_pages.ts @@ -1,25 +1,14 @@ import { RecentsEntry } from "../types/recents" - -const RECENTS_KEY = "recent_pages" - -export async function ensure_recents_exists(): Promise { - const data = await chrome.storage.local.get(RECENTS_KEY) - if (!data[RECENTS_KEY]) { - await chrome.storage.local.set({ [RECENTS_KEY]: [] }) - } -} +import { recents_item } from "./items" export async function add_url_to_recents(url: string, name: string): Promise { - await ensure_recents_exists() - // clean URL (remove media queries and page fragments) url = url.split("#")[0].split("?")[0] - let data = await chrome.storage.local.get(RECENTS_KEY) - let recents: RecentsEntry[] = data[RECENTS_KEY] as RecentsEntry[] || [] + let recents = await recents_item.getValue() const now = Date.now() - - let existing_entry: RecentsEntry | undefined = recents.find(entry => entry.url === url) + + const existing_entry: RecentsEntry | undefined = recents.find(entry => entry.url === url) if (existing_entry) { existing_entry.viewed_count += 1 existing_entry.last_viewed_timestamp = now @@ -39,17 +28,13 @@ export async function add_url_to_recents(url: string, name: string): Promise b.last_viewed_timestamp - a.last_viewed_timestamp) - await chrome.storage.local.set({ [RECENTS_KEY]: recents }) + await recents_item.setValue(recents) } export async function get_recents(): Promise { - await ensure_recents_exists() - - let data = await chrome.storage.local.get(RECENTS_KEY) - let recents: RecentsEntry[] = data[RECENTS_KEY] as RecentsEntry[] || [] - return recents + return await recents_item.getValue() } export async function clear_recents(): Promise { - await chrome.storage.local.set({ [RECENTS_KEY]: [] }) + await recents_item.setValue([]) }