npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@liminal-screen/api

v0.5.0

Published

IPC bridge for Liminal Screen remote options pages. Works as npm package or CDN script.

Readme

Liminal Screen API

IPC bridge for Liminal Screen remote options pages. Works as an npm package or a CDN-loaded script — no @tauri-apps/api dependency required.

Overview

The Liminal Screen API lets remote options pages communicate with the Tauri backend via __TAURI__ globals (requires withGlobalTauri: true in tauri.conf.json). It auto-detects whether it's running inside a Tauri window or a regular browser and falls back to mock data when outside Tauri.

Features

  • Cross-environment: Works in Tauri webviews and plain browsers (mock mode)
  • TypeScript: Full types for AppOptions, SetOptionsPayload, CustomOptions, UpdateInfo
  • Reactive store: createOptionsStore() provides a Signal-based reactive state kept in sync with the backend
  • Native dialogs: ask() and showMessage() use Tauri's dialog plugin when available, fall back to confirm()/alert()
  • External links: openUrl() opens links in the user's real browser instead of hijacking the options window
  • Window control: closeOptions() lets the page dismiss its own window
  • Screensaver preview: previewScreensaver() opens a windowed preview of the configured saver URL
  • Event sync: startAutoSync() pushes real-time option updates from the backend
  • System screensaver control: detect a conflicting OS screensaver and disable/restore it so Liminal is the only screensaver
  • Media detection: isMediaActive() reports when the saver is suppressed, getMediaBlockerName() names the process responsible
  • App updates: checkForUpdates(), installUpdate() and an update-available subscription
  • App version: getVersion() returns the running application version
  • Zero dependencies: No @tauri-apps/api needed — uses window.__TAURI__ globals directly

Installation

npm

npm install @liminal-screen/api
import { liminalAPI, createOptionsStore } from '@liminal-screen/api';

CDN (no build step)

<script src="https://unpkg.com/@liminal-screen/api/dist/liminal-api.global.js"></script>
<script>
  const { liminalAPI, createOptionsStore } = LiminalAPI;
  // ...
</script>

Pin an exact version in production — e.g. @liminal-screen/[email protected].

Quick Start

Basic — imperative API

const options = await liminalAPI.getOptions();
console.log(options.appName, options.startsIn);

await liminalAPI.setOptions({ startsIn: 5, debug: true });

const defaults = await liminalAPI.resetOptions();

Reactive — with options store

import { createOptionsStore } from '@liminal-screen/api';

const store = createOptionsStore(liminalAPI);

// Re-render whenever options change
store.signal.effect((opts) => {
  if (!opts) return;
  document.getElementById('starts-in').value = opts.startsIn;
  document.getElementById('app-name').textContent = opts.appName;
});

// Save form data
await store.save(collectedFormData);

// Reset to defaults
await store.reset();

// Clean up on unload
window.addEventListener('beforeunload', () => store.destroy());

Dialogs

// Confirm before resetting
if (!await liminalAPI.ask('Reset all options to defaults?', { title: 'Reset', kind: 'warning' })) {
  return;
}
await liminalAPI.resetOptions();

// Show a success message
await liminalAPI.showMessage('Settings saved!', { title: 'Saved', kind: 'info' });

External links and preview

A remote options page is loaded in a webview, so a plain <a href> or window.open() either replaces your options page or is silently blocked. Use openUrl() to hand the link to the user's real browser:

document.getElementById('docs-link').addEventListener('click', (e) => {
  e.preventDefault();
  liminalAPI.openUrl('https://example.com/docs');
});

// Optionally pick the application to open it with
await liminalAPI.openUrl('mailto:[email protected]');

previewScreensaver() opens the configured saver URL (saverUrlDebug when debug is on) in its own resizable window, so users can see the effect of their settings without waiting for the idle timer:

document.getElementById('preview-btn').addEventListener('click', async () => {
  try {
    await liminalAPI.previewScreensaver();
  } catch (e) {
    await liminalAPI.showMessage(e.message, { kind: 'error' });
  }
});

Save and close

document.getElementById('done-btn').addEventListener('click', async () => {
  await store.save(collectedFormData);   // the window takes unsaved state with it
  await liminalAPI.closeOptions();
});

System screensaver conflict

Liminal is meant to be the only screensaver — a system screensaver on an overlapping timer draws over Liminal. Detect one and offer to disable it (the prior timeout is saved so it can be restored):

const os = await liminalAPI.getOsScreensaverStatus();
if (os.detected && os.enabled) {
  // e.g. os.idleSeconds === 60 → the OS screensaver starts after 1 minute
  if (await liminalAPI.ask('Your system screensaver may appear over Liminal. Disable it?')) {
    await liminalAPI.disableOsScreensaver();
  }
}

// Offer to undo it later — non-null means Liminal disabled it:
const saved = await liminalAPI.getSavedOsScreensaverIdle();
if (saved != null) {
  await liminalAPI.restoreOsScreensaver();
}

Why hasn't the saver started?

isMediaActive() alone tells you that something is suppressing the saver — genuine media playback is usually obvious to the user, but an idle background app holding the same kind of assertion is not, so pair it with getMediaBlockerName() to say what:

setInterval(async () => {
  const blocked = await liminalAPI.isMediaActive();
  if (!blocked) {
    statusEl.textContent = '';
    return;
  }
  const who = await liminalAPI.getMediaBlockerName();
  statusEl.textContent = who
    ? `${who} is blocking ${appName} from starting.`
    : 'Something is blocking the screensaver from starting.';
}, 5000);

App updates

// React to the startup check as well as manual checks
liminalAPI.onUpdateAvailable((info) => {
  banner.textContent = `Version ${info.version} is available`;
});

// Manual check, gated behind a user action
const update = await liminalAPI.checkForUpdates();
if (update && await liminalAPI.ask(`Install v${update.version} now? The app will restart.`)) {
  await liminalAPI.installUpdate();
}

App version

document.getElementById('version').textContent = `v${await liminalAPI.getVersion()}`;

API Reference

liminalAPI — singleton instance

| Method | Returns | Description | |--------|---------|-------------| | getOptions() | Promise<AppOptions> | Get current options from backend | | setOptions(payload) | Promise<void> | Save user options (identity fields preserved) | | resetOptions() | Promise<AppOptions> | Reset to .env defaults | | previewScreensaver() | Promise<void> | Open a preview window for the configured saver URL | | openUrl(url, openWith?) | Promise<void> | Open an external URL in the user's default browser/app | | closeOptions() | Promise<void> | Close the options window this page runs in | | getVersion() | Promise<string> | Running app version (e.g. "0.3.0") | | getOsScreensaverStatus() | Promise<OsScreensaverStatus> | Read the OS-native screensaver config (conflict detection) | | disableOsScreensaver() | Promise<void> | Disable the OS screensaver so it can't cover Liminal (prior value saved) | | restoreOsScreensaver() | Promise<void> | Restore the OS screensaver to the saved value | | getSavedOsScreensaverIdle() | Promise<number \| null> | Saved OS timeout (seconds) if Liminal disabled it, else null | | isMediaActive() | Promise<boolean> | true when a video/call is holding a display-sleep assertion, blocking the saver | | getMediaBlockerName() | Promise<string \| null> | Name of the process responsible (e.g. "LocalSend"), or null if none | | ask(message, options?) | Promise<boolean> | Confirmation dialog (falls back to confirm()) | | showMessage(message, options?) | Promise<void> | Message dialog (falls back to alert()) | | checkForUpdates() | Promise<UpdateInfo \| null> | Check for an app update; null when none (or outside Tauri) | | installUpdate() | Promise<void> | Download and install a pending update, then restart | | onUpdateAvailable(callback) | () => void | Subscribe to update-available events | | startAutoSync(callback) | Promise<() => void> | Subscribe to real-time option updates | | onOptionsUpdate(callback) | () => void | Listen on window event bus (works outside Tauri) | | destroy() | void | Clean up all listeners | | isInTauri | boolean | true when running inside Tauri |

Also exported: createOptionsStore, Signal, LiminalAPIError, and the LiminalAPI class itself for multi-instance setups.

createOptionsStore(api) — reactive store

Returns { signal, save, reset, destroy } where signal is a Signal<AppOptions | null>.

AppOptions type

interface AppOptions extends MandatoryOptions {
  saverUrl: string;           // Production screensaver URL (read-only)
  saverUrlDebug: string;      // Debug screensaver URL (read-only)
  optionsUrl: string;         // Remote options URL (read-only)
  appName: string;            // Fork display name (read-only)
  appDescription: string;     // Fork description (read-only)
  customOptions: CustomOptions;      // Fork-defined key/value pairs
  instanceId: string;                // Instance UUID (read-only, reset on factory reset)
  notificationsEnabled: boolean;     // User consent for feed notifications
  notificationUrl: string;           // Notification feed URL (read-only; empty = disabled)
  notificationCheckIntervalSecs: number; // Poll interval (read-only)
  autostart: boolean;                // Start at login (reflects the OS login item)
}

interface MandatoryOptions {
  startsIn: number;            // Minutes before activation
  displayOffIn: number;        // Minutes before display off
  requirePassIn: number;       // Minutes before lock (0 = disabled)
  runOnBattery: boolean;       // Run on battery power
  debug: boolean;              // Use debug URL
  notificationsEnabled?: boolean; // Opt-in; omit to keep current consent
  autostart?: boolean;         // Omit to keep the current login-item state
}

type CustomOptions = Record<string, string | number | boolean>;

setOptions() merges the payload over the current options, so the optional fields can be omitted safely. Read-only fields are re-applied by the backend and cannot be changed from the options page.

OsScreensaverStatus type

interface OsScreensaverStatus {
  detected: boolean;          // Could the setting be read on this platform/desktop?
  enabled: boolean;           // Is the OS screensaver set to activate on a timer?
  idleSeconds: number | null; // Idle seconds before it starts; null if disabled/unknown
}

UpdateInfo type

interface UpdateInfo {
  version: string;            // Version of the available update
  notes?: string;             // Release notes, when the release provides them
}

App Compatibility

This package version is independent of the Liminal Screen app version — it tracks its own JavaScript API surface. But each method calls into the app's backend, so a few need a recent enough app build. Outside Tauri everything falls back to mock behaviour, so this only matters for the installed app your fork ships.

| Feature | Requires app | Notes | |---------|--------------|-------| | Options, reset, custom fields | any | App-defined commands aren't ACL-gated, so these work on any build | | Updater, notification and autostart fields | 0.2.0+ | | | previewScreensaver() | 0.2.0+ | Uses the create_preview_window command | | openUrl(), ask(), showMessage(), startAutoSync() | 0.3.0+ | Need their permission granted to the options page's remote origin; the app registers that grant at runtime from VITE_OPTIONS_URL. On older builds, package 0.3.1+ logs a warning and falls back instead of throwing | | closeOptions() | 0.3.0+ | Needs the close_options command; rejects with LiminalAPIError on older builds | | isMediaActive(), getMediaBlockerName() | unreleased (after 0.2.0) | Need the is_media_active/get_media_blocker_name commands; reject with LiminalAPIError on older builds. Resolve false/null (not an error) on Windows/Linux, where detection isn't implemented |

Fork developers: if you maintain your own src-tauri/capabilities/options.json, note that listing a permission isn't enough — Tauri scopes capabilities to local content, and your options page is a remote origin. See Remote origins and the ACL.

Documentation

Reference Implementation

See examples/remote-options/ for a complete options page with form handling, reactive store, native dialogs, and service worker.

Development

# Build (ESM + IIFE + types)
bun run build

# Typecheck
bun run typecheck

# Tests (run from the repository root)
bun run test

License

MIT — see LICENSE.