{"id":20942194,"url":"https://github.com/getstation/hookstate-plugin-web-extension","last_synced_at":"2025-07-26T21:33:23.346Z","repository":{"id":66202674,"uuid":"334966150","full_name":"getstation/hookstate-plugin-web-extension","owner":"getstation","description":"Sync and persist state between background page and content_script using browser.storage","archived":false,"fork":false,"pushed_at":"2021-02-24T16:31:43.000Z","size":92,"stargazers_count":3,"open_issues_count":0,"forks_count":1,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-13T03:43:34.652Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/getstation.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-02-01T13:54:54.000Z","updated_at":"2022-11-28T23:23:25.000Z","dependencies_parsed_at":"2023-03-01T09:16:12.019Z","dependency_job_id":null,"html_url":"https://github.com/getstation/hookstate-plugin-web-extension","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/getstation/hookstate-plugin-web-extension","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getstation%2Fhookstate-plugin-web-extension","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getstation%2Fhookstate-plugin-web-extension/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getstation%2Fhookstate-plugin-web-extension/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getstation%2Fhookstate-plugin-web-extension/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/getstation","download_url":"https://codeload.github.com/getstation/hookstate-plugin-web-extension/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getstation%2Fhookstate-plugin-web-extension/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267243623,"owners_count":24058949,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-26T02:00:08.937Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-18T23:24:22.387Z","updated_at":"2025-07-26T21:33:23.291Z","avatar_url":"https://github.com/getstation.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# hookstate-plugin-web-extension\n\n## Install\n```bash\nnpm i hookstate-plugin-web-extension\n# or\nyarn add hookstate-plugin-web-extension\n```\n\n## Features\n- Compatible with _chrome.storage.*_ and _browser.storage.*_\n- State synced between background page and all content scripts through _browser/chrome.storage_\n- Configurable persistence. Choose which keys of the storage you want to load at init\n- Initial synchronous state. It will be erased upon init if persisted data is found in storage\n- Use an internal `__state_update` key upon each storage update to pass metadata between processes.\n  This allows processes receiving the changes to apply only necessary updates on part of their tree to avoid unnecessary rerendering.\n  See [details below](#forward-partial-updates-of-the-tree-through-browser-storage)\n- TODO: Migrations between state versions\n\n## Usage\n\n### Background page\n```ts\nimport { BrowserExtensionStorage } from 'hookstate-plugin-web-extension';\n\nimport { GlobalState } from './mystate/global/types';\nimport { initialState } from './mystate/global/initial';\n\nconst state = createState\u003cGlobalState\u003e(initialState);\n\nstate.attach(\n  BrowserExtensionStorage({\n    // either browser.storage or chrome.storage\n    storage: browser.storage,\n    // either 'local' or 'sync'\n    areaName: 'local',\n    // id of the plugin instance, must be different for each process\n    id: 'background',\n    // your initial state. All keys represented in your GlobalState type MUST be present in this one\n    initialState: initialState,\n    // true for background page, false for all others\n    leader: true,\n    // which persisted keys of GlobalState do you want to reload upon start?\n    persistedKeys: [],\n    // what is the version of your state. This needs to be updated if you need to run a migration script (TODO)\n    version: 1,\n    // called whenever the plugin encounters an Error\n    onError: notify,\n  }),\n);\n```\n\n### Other processes (content scripts, popups)\n```ts\nimport { BrowserExtensionStorage } from 'hookstate-plugin-web-extension';\n\n// same type and initial state as for the background page\nimport { GlobalState } from './mystate/global/types';\nimport { initialState } from './mystate/global/initial';\n\nconst state = createState\u003cGlobalState\u003e(initialState);\n\nstate.attach(\n  BrowserExtensionStorage({\n    storage: browser.storage,\n    areaName: 'local',\n    // MUST be different for each process\n    id: 'someRandomId',\n    initialState: initialState,\n    leader: false,\n    onError: notify,\n  }),\n);\n```\n\n## Forward partial updates of the tree through browser storage\nThe [Storage API](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage/StorageArea/set) only\nallows us to update root values. Here is an example:\n```ts\nbrowser.storage.local.set({\n  a: 'a1',\n  b: {\n    c: 1,\n    d: 2,\n  }\n})\n```\n\nIf I want to update `b.c`, with this API, I must set the whole `b` tree:\n```ts\nbrowser.storage.local.set({\n  b: {\n    c: 2,\n    d: 2,\n  }\n})\n\nbrowser.storage.onChanged.addListener(changes =\u003e {\n  // here we will have the whole `changes.b` tree, so the simple solution would be:\n  hookstateState.b.set(changes.b.newValue)\n});\n```\n\nAnd this is not good, because it means that we get a new `b.d` object, and it will be considerer as updated by hookstate.\nIn this case it is not particularly a problem, by imagine a list with hundreds of items updated whenever you update one,\nyou'd loose all the optimisations of hookstate.\n\nIn this extension we are smarter than this. As a matter of fact we can abuse the storage API to use a custom key (`__state_update`)\nupon each update of the state, which value contains necessary metadata to only apply necessary changes onto the target states.\n\nUpon changes, this `__state_update` is computed and sent along the real state update through the `storage.set` method.\nWhen other processes receive those changes, they know that:\n- If this key is present, it means the update comes from this plugin\n- We can use this computed metadata to apply more precise `state.merge` or `state.set` on the state, so that it reflects\n  what has been done in the originating process.\n\n## License\n[MIT](http://mit-license.org)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgetstation%2Fhookstate-plugin-web-extension","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgetstation%2Fhookstate-plugin-web-extension","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgetstation%2Fhookstate-plugin-web-extension/lists"}