Storage and deep links
Storage and deep links
The localStorage trap
Browser storage is not durable in a packaged Peko app. The asset server binds
a fresh port on every launch, so the page origin changes, and every per-origin
store goes with it: localStorage, sessionStorage, IndexedDB, and cookies.
What makes this dangerous is the timing. It works in peko run, because the
framework dev server has a stable origin. It works in a deployed SSR app, because
that loads from a stable https:// origin. It breaks only in the packaged
static build, which is to say after you ship.
Use the native surface instead:
await peko.storage.set({ key: 'theme', value: 'dark' })
const theme = await peko.storage.get({ key: 'theme' })pekoui::storage keeps a JSON file in the app data directory. The bridge methods
are storage.get, set, remove, keys, and clear. Values are stored as raw
JSON text, and get answers the literal null when a key is absent.
For anything with its own shape, write a native handler that owns a file under
ui::paths::app_data_dir().
keychain
For values that should not sit in plain text, pekoui::keychain provides an
encrypted per-app store:
await peko.keychain.set({ name: 'token', value: secret })
const token = await peko.keychain.get({ name: 'token' })
await peko.keychain.remove({ name: 'token' })Secrets are sealed with authenticated encryption, so a value that has been tampered with fails to decrypt rather than returning corrupted data. Use it for tokens, credentials, and anything else you would not want readable in the app's data directory.
The bridge methods are keychain.get, set, and remove, and the PekoScript
side is ui::keychain if you would rather reach it from a native handler.
Paths
ui::paths::app_data_dir() returns the per-app directory, created if missing,
derived from the bundle identifier.
Deep links
Declare a scheme in the manifest:
[ui]
scheme = "myapp"run() registers it and installs a handler, so myapp://settings/profile opens
the app and delivers the route. On the web side, subscribe:
peko.on('navigate', (data) => {
if (data?.path) router.navigate(data.path)
})Delivery differs per platform: macOS gets a live system event, Windows and Linux receive it on the command line and forward it to a running instance, and iOS delivers it only after the UI has connected. The SDK smooths this over and replays a pending route to the first subscriber, so a router that mounts late does not miss the launch route.
By default a second launch forwards its URL to the running window and exits.
allow_multiple_instances() turns that off when your app genuinely wants
several windows, each with its own state.
env
ui::env reads and writes process environment variables and reports the OS.
A practical note: an app launched from Finder or the Dock inherits a minimal
PATH that does not include developer tooling. If you spawn subprocesses, resolve
the user's real PATH once through their shell and pass it to the children.