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

@moonbase.sh/electron

v0.2.2

Published

Moonbase.sh licensing for Electron apps, with a finished activation UI

Readme

@moonbase.sh/electron

Moonbase licensing for an Electron app, on top of @moonbase.sh/licensing: the counterpart of moonbase-cpp's JUCE module and tauri-plugin-moonbase. It validates the stored license at startup, runs browser and offline activation, trials, deactivation, add-ons and update downloads, and presents a finished activation UI whenever the app is locked: no renderer code required. Create it in the main process. It needs Electron 30 or later.

Install

pnpm add @moonbase.sh/electron

Set up

// main.ts
import { createMoonbase } from '@moonbase.sh/electron'

const moonbase = createMoonbase({
  endpoint: 'https://your-store.moonbase.sh',
  productId: 'your-product',
  publicKey: MOONBASE_PUBLIC_KEY,
  ui: {
    productName: 'Solstice',
    manufacturerName: 'Helio Audio',
    accent: '#186cdc',
    logoSvg: SOLSTICE_LOGO_SVG,
    trialFeatures: [
      { label: 'Full DSP engine, unrestricted', included: true },
      { label: 'Use in commercial releases', included: false },
    ],
  },
})
moonbase.start()

The license is kept in license.mb in the app's user data folder, and the app's own version (app.getVersion()) is the version reported to Moonbase and the one a license that covers some versions has to cover.

Bundling the main process

The module loads its activation UI from the folder it runs from. Left out of the main process's bundle, it finds the UI in node_modules. Bundled, as Electron Forge's Vite template does with every dependency, it needs the UI copied beside the bundle, which the module's Vite plugin does. Add it to the main process's Vite config:

// vite.main.config.ts
import { moonbase } from '@moonbase.sh/electron/vite'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [moonbase()],
})

With another bundler, leave the module out of the bundle, or copy the package's dist/ui folder and dist/ui-preload.cjs to the folder the bundled module runs from. If they are not there, start() throws and names the folder it looked in.

The activation UI

| ui option | Default | | | --- | --- | --- | | productName | app.getName() | Shown throughout | | manufacturerName | none | Under the product name, and as the account the customer signs in to | | accent | #186cdc (Moonbase blue) | The one brand colour: buttons, progress, step numbers. Any CSS colour | | logoSvg, logoPng | a sun mark in the accent | The product mark in the header. logoPng takes bytes or a path | | trialFeatures | none | The trial screen's included and excluded lines | | trialLengthDays | 14 | Shown when the license does not say | | showAddOns | off | An "Add-ons" row in the license details, to try, buy and activate the product's add-ons | | strings | built-in copy | Override any heading or body, including the copy for each reason the app locked | | showMoonbaseBadge | on | The "Licensing secured by moonbase" footer | | autoPresentOnLock | on | Present the UI when the app starts unlicensed, and whenever it loses its license | | autoPresentUpdate | on | Present the update screen when the app starts with a newer release it was not told to skip | | reduceMotion | off | Skip transitions; the system setting is honoured either way | | windowTitle | the product name | | | presentation | window | window, or overlay to lock one of the app's windows; see below | | window | the app's first window | The window the overlay locks, and the activation window's parent |

Pass ui: false to turn the built-in UI off and build your own on the renderer API below.

The screens are the same in every Moonbase SDK: Welcome (activate online, or offline; once the app has lost its license it says why), Waiting for activation (with the link to copy when the browser can't be opened), You're all set, Offline activation (save the machine file, then drop or pick the license file), Trial and Trial expired, Upgrade needed (for a license that doesn't cover this version), License details (with the license id to copy for support, seats and Deactivate), Add-ons, and Update available (release notes, a download with progress, and Skip this version).

Show it yourself, optionally on a screen (license, update, offline or addons):

moonbase.present('license')

Locking the app

By default the UI is its own window, and the app stays usable beside it, which suits an app with free features. To lock the app instead, present the UI over one of its windows with presentation: 'overlay'. The same screens then float over the app's content, which is blurred behind them and takes no clicks or keys. While the app has no license the overlay cannot be dismissed; once it has one it gets a close button and closes on Escape.

The overlay is a view of the module's own laid over the window, so it works whatever the app's page does, and it survives the page reloading. It locks one window: the one ui.window names, or the app's first. When that window closes while the app is locked, the overlay moves to the next window the app opens, such as the one a macOS app opens again from the dock. The app's other windows stay usable, so keep gating in the main process either way: the overlay is a lock on the interface.

Gating

Check the license in the main process, where a renderer cannot skip it:

ipcMain.handle('export', async () => {
  if (!moonbase.isLicensed()) {
    void moonbase.present()
    return { error: 'Exporting needs a license.' }
  }
  // ...
})

isLicensed() is true with a license or a trial, and cheap enough to check anywhere. hasSubProduct(id) says whether to unlock an add-on, and license() returns the full license for other checks. Every method of ActivationController is on moonbase too.

The renderer API

To show the license state in the app's own windows, or to build your own UI, expose window.moonbase from the window's preload script:

// preload.ts
import { exposeMoonbase } from '@moonbase.sh/electron/preload'

exposeMoonbase()
// renderer
import type { ActivationState } from '@moonbase.sh/electron/renderer'

function render(state: ActivationState) {
  document.querySelector('#license')!.textContent = state.licensed ? `Licensed to ${state.license?.issuedTo.name}` : 'Not activated'
}

window.moonbase.onStateChanged(render)
render(await window.moonbase.state())
await window.moonbase.activate()

@moonbase.sh/electron/renderer has the types for renderer code, and importing from it types window.moonbase. To type window.moonbase without importing anything, reference it once from a declaration file:

/// <reference types="@moonbase.sh/electron/renderer" />

Leave it out if you expose the API under another name with exposeMoonbase(key).

A sandboxed preload can require nothing but Electron, so bundle it, as Electron Forge and electron-vite do. The API has the same calls as tauri-plugin-moonbase's: state, activate, cancelActivation, refresh, deactivate, forget, deviceToken, activateOffline, dismissError, device, checkUpdate, updateDownloadUrl, present, closeUi, upgradeLicense, loadAddOns, refreshAddOns, activateSubProduct, requestSubProductTrial, buySubProduct and hasSubProduct. A failed call rejects with { kind, message }, where kind is the SDK's ErrorType (ApiError, LicenseInvalid, ReleaseNotCovered, ...) or notLicensed, busy, internal or forbidden.

Every renderer the preload is in may call all of these. To limit them, pass rendererAccess: read-only allows state, device, checkUpdate and present, none nothing, and a function decides per window. The built-in UI always gets what it needs.

The Electron sample in the Moonbase repository (samples/electron-licensing) is Aurora, a small app whose export is a paid feature, wired to Moonbase's public demo store so it activates out of the box.

License: MIT