electron-nano-store
v0.6.0
Published
A minimalistic, secure, type-safe data store for Electron
Downloads
26
Maintainers
Readme
Nano Electron store
A minimalistic, secure, type-safe data store for Electron. This package is flat wrapper around fs-nano-store and with a few strict checks to allow safely use it in renderer.
Note See the full fs-nano-store documentation for more information about how store works
Installation
npm install electron-nano-store
or
pnpm add electron-nano-store
or
yarn add electron-nano-store
Usage
Simple
Just expose in main world defineStore
function and use it directly in your renderer anywhere.
// In Electron Preload Script
import { defineStore } from "electron-nano-store"
import { contextBridge } from 'electron'
contextBridge.exposeInMainWorld('defineStore', defineStore)
// In Renderer
const store = await defineStore('user')
store.set('role', 'admin')
console.log(store.get('role')) // -> 'admin'
Recommended
As an additional safety precaution, you can choose not to expose a defineStore
function, but instead expose an already
defined store.
The examples below also show an example of use with TypeScript.
// contracts.ts
export type UserStore = {
role: 'admin' | 'user'
}
// in Preload Script
import { defineStore } from "electron-nano-store"
import { contextBridge } from 'electron'
import type { UserStore } from 'contracts.ts'
const userStorePromise = defineStore<UserStore>('user')
contextBridge.exposeInMainWorld('userStorePromise', userStorePromise)
// In Renderer
import type { UserStore } from 'contracts.ts'
import type { defineStore } from 'electron-nano-store'
declare global {
interface Window {
userStorePromise: ReturnType<typeof defineStore<UserStore>>
}
}
const store = await window.userStorePromise
store.set('role', 'admin')
console.log(store.get('role')) // -> 'admin'
store.set('role', 'wrong-role') // TS Error: Argument of type '"wrong-role"' is not assignable to parameter of type '"admin" | "user"'
Recommended with automatic type inference
The exchange of types between the preload and the renderer can be a little annoying and complicated. So you can use unplugin-auto-expose for automatic type inference
// in Preload Script
import { defineStore } from "electron-nano-store"
type UserStore = {
role: 'admin' | 'user'
}
export const userStorePromise = defineStore<UserStore>('user')
// In Renderer
import { userStorePromise } from '#preload'
const store = await userStorePromise
In Main
This package was intentionally designed with many restrictions for use in preload. If you want to use it in main, or you need more control you should use fs-nano-store directly
// In Main
import { defineStore } from 'fs-nano-store'
import { resolveStoreFilepath } from 'electron-nano-store'
import { app } from 'electron'
const store = await defineStore(
resolveStoreFilepath(
'store-name',
app.getPath('userData')
)
)
Listen store changes in Renderer
fs-nano-store automatically tracks all changes to the store, and emit a changed
event if the store has been changed
out of context.
However, electron does not allow you to expose EventEmitter
to the main world. This means that even though it defines
and returns change
property, you can't add listeners directly.
const { changes } = defineStore('store-name')
changes.addListener // undefined
To do this, you must define store in the preload context, add listeners there, and proxy all events to an
existing EventTarget
, such as a window
// in Preload Script
const storePromise = defineStore<Store>('user')
storePromise.then(({ changes }) => {
changes.addListener(
'changed',
() => globalThis.dispatchEvent(new CustomEvent('user:changed')),
)
})
// in Renderer
globalThis.addEventListener(
'user:changed',
() => { /* ... */
}
)
Security limitation
- You can't somehow change where are storage files placed in filesystems.
It always in
electron.app.getPath('userData')
directory defined by electron. Since thedefineStore
function may be exposed to a non-secure context, this is done to prevent malicious code from making uncontrolled writes anywhere on the file system.// 🚫 Harmful use defineStore('.privat-config', { customPath: '/somewhere/in/user/filesystem/' })
Note If you need create store in some different location, you should make your own wrapper around fs-nano-store. Look how use In Main.
- For the same reasons, you cannot use any path fragments in the repository name
// 🚫 Harmful use defineStore('../../somewhere/in/user/filesystem/privat-config')