@napplet/sdk
v0.2.1
Published
Typed named exports wrapping window.napplet for bundler-consuming napplet developers
Maintainers
Readme
@napplet/sdk
Named TypeScript exports for napplet developers using a bundler. Wraps
window.nappletat call time.
Getting Started
Prerequisites
@napplet/shimmust be imported (side-effect) to installwindow.nappletbefore SDK methods are called- A shell host running a napplet protocol shell implementation
How It Works
- Import
@napplet/shimin your entry point to install thewindow.nappletglobal - Import named exports from
@napplet/sdk--relay,ipc,storage,keys - Each SDK method delegates to its
window.napplet.*counterpart at call time - If
window.nappletis not installed when a method is called, a descriptive error is thrown
Installation
npm install @napplet/sdk @napplet/shimQuick Start
import '@napplet/shim';
import { relay, ipc, storage, keys, media, notify, config, type NostrEvent } from '@napplet/sdk';
// Subscribe to kind 1 notes
const sub = relay.subscribe(
{ kinds: [1], limit: 20 },
(event) => console.log('New note:', event.content),
() => console.log('End of stored events'),
);
// Publish a signed note
const signed = await relay.publish({
kind: 1,
content: 'Hello from my napplet!',
tags: [],
created_at: Math.floor(Date.now() / 1000),
});
// Inter-pane messaging
ipc.emit('chat:message', [], JSON.stringify({ text: 'hi' }));
const ipcSub = ipc.on('bot:response', (payload) => {
console.log('Bot says:', payload);
});
// Scoped storage
await storage.setItem('theme', 'dark');
const theme = await storage.getItem('theme'); // 'dark'
// Register keyboard action
const result = await keys.registerAction({
id: 'editor.save', label: 'Save', defaultKey: 'Ctrl+S',
});
// Listen for bound key locally
const keySub = keys.onAction('editor.save', () => {
console.log('Save triggered!');
});
// Create a media session
const { sessionId } = await media.createSession({
title: 'My Song', artist: 'The Artist',
});
media.reportState(sessionId, { status: 'playing', position: 42.5, duration: 240 });
// Send a notification
const { notificationId } = await notify.send({
title: 'Task complete', body: 'Build succeeded', priority: 'normal',
});
notify.badge(1);
// Read per-napplet config (shell-validated + defaulted)
const values = await config.get();
console.log('Current theme:', values.theme);
// Subscribe to live config updates
const configSub = config.subscribe((v) => {
applyTheme(v.theme);
});
// Deep-link settings UI
config.openSettings({ section: 'appearance' });
// Clean up
sub.close();
ipcSub.close();
keySub.close();
configSub.close();API Reference
relay
Relay operations through the shell's relay pool. Mirrors window.napplet.relay.
| Method | Returns | Description |
|--------|---------|-------------|
| subscribe(filters, onEvent, onEose, options?) | Subscription | Open a live relay subscription through the shell's relay pool |
| publish(template, options?) | Promise<NostrEvent> | Send event template to the shell for signing and broadcast |
| publishEncrypted(template, recipient, encryption?) | Promise<NostrEvent> | Send event template for encryption, signing, and broadcast |
| query(filters) | Promise<NostrEvent[]> | One-shot query: subscribe, collect until EOSE, resolve |
ipc
Inter-napplet communication between napplets. Mirrors window.napplet.ipc.
Messages are sent as JSON envelope objects ({ type: 'ifc.emit', topic, payload }) and received as
({ type: 'ifc.event', topic, payload, sender }).
| Method | Returns | Description |
|--------|---------|-------------|
| emit(topic, extraTags?, content?) | void | Broadcast an IFC event to other napplets via the shell |
| on(topic, callback) | { close(): void } | Subscribe to IFC events on a topic |
storage
Sandboxed key-value storage. Mirrors window.napplet.storage. 512 KB quota per napplet.
| Method | Returns | Description |
|--------|---------|-------------|
| getItem(key) | Promise<string \| null> | Retrieve a stored value |
| setItem(key, value) | Promise<void> | Store a key-value pair |
| removeItem(key) | Promise<void> | Remove a stored key |
| keys() | Promise<string[]> | List all stored keys |
media
Media session control. Mirrors window.napplet.media.
| Method | Returns | Description |
|--------|---------|-------------|
| createSession(metadata?) | Promise<{ sessionId }> | Create a new media session with optional metadata |
| updateSession(sessionId, metadata) | void | Update metadata for an existing session |
| destroySession(sessionId) | void | Destroy a session |
| reportState(sessionId, state) | void | Report playback state |
| reportCapabilities(sessionId, actions) | void | Declare supported media actions |
| onCommand(sessionId, callback) | { close(): void } | Listen for shell media commands |
| onControls(sessionId, callback) | { close(): void } | Listen for the shell's supported control list |
notify
Shell-rendered notifications. Mirrors window.napplet.notify.
| Method | Returns | Description |
|--------|---------|-------------|
| send(notification) | Promise<{ notificationId }> | Send a notification to the shell |
| dismiss(notificationId) | void | Dismiss a notification |
| badge(count) | void | Set badge count (0 to clear) |
| registerChannel(channel) | void | Register a notification channel |
| requestPermission(channel?) | Promise<{ granted }> | Request permission to send notifications |
| onAction(callback) | { close(): void } | Listen for action button clicks |
| onClicked(callback) | { close(): void } | Listen for notification body clicks |
| onDismissed(callback) | { close(): void } | Listen for dismissals |
| onControls(callback) | { close(): void } | Listen for shell's notification capabilities |
config
Per-napplet declarative configuration (NUB-CONFIG). Mirrors window.napplet.config.
| Method | Returns | Description |
|--------|---------|-------------|
| get() | Promise<Record<string, unknown>> | One-shot snapshot of validated + defaulted config values |
| subscribe(callback) | { close(): void } | Live push stream (initial snapshot + updates on change) |
| openSettings(options?) | void | Open shell's settings UI, optionally deep-linked to x-napplet-section |
| registerSchema(schema, version?) | Promise<void> | Runtime schema registration (escape hatch; prefer vite-plugin configSchema) |
| onSchemaError(callback) | () => void | Listen for config.schemaError pushes (returns plain teardown fn) |
| schema (accessor) | Record<string, unknown> \| null | Readonly current schema |
FromSchema type inference (NUB-CONFIG)
json-schema-to-ts is declared as an optional peerDependency of @napplet/nub-config. Install it in your napplet to get FromSchema<typeof schema> typing for your config.subscribe callback -- the values parameter is inferred directly from your schema (enums, required fields, defaults all flow through). Authors who skip json-schema-to-ts pay no install cost and config.subscribe still works with the default Record<string, unknown> typing.
import '@napplet/shim';
import { config } from '@napplet/sdk';
import type { FromSchema } from 'json-schema-to-ts';
const schema = {
type: 'object',
properties: {
theme: { type: 'string', enum: ['light', 'dark'], default: 'dark' },
},
required: ['theme'],
} as const;
type MyConfig = FromSchema<typeof schema>;
const sub = config.subscribe((values: MyConfig) => {
// values.theme is typed 'light' | 'dark'
});Install the peer when you want typed callbacks:
npm install --save-dev json-schema-to-tskeys
Keyboard forwarding and action keybindings. Mirrors window.napplet.keys.
| Method | Returns | Description |
|--------|---------|-------------|
| registerAction(action) | Promise<{ actionId, binding? }> | Declare a named action the shell can bind to a key |
| unregisterAction(actionId) | void | Remove a previously registered action |
| onAction(actionId, callback) | { close(): void } | Register a local handler for a bound key (zero-latency, not a wire message) |
shell
Namespaced capability query. Access via window.napplet.shell.supports() after importing @napplet/shim.
Note: The SDK does not export a top-level
shellobject. Usewindow.napplet.shell.supports()directly.
| Method | Returns | Description |
|--------|---------|-------------|
| supports(capability) | boolean | Check shell support for a NUB (nub:relay) or permission (perm:popups). Bare NUB names also accepted (relay). |
Example:
import '@napplet/shim';
// NUB domains (bare shorthand or nub: prefix)
if (window.napplet.shell.supports('relay')) { /* ... */ }
if (window.napplet.shell.supports('nub:identity')) { /* ... */ }
// Permissions
if (window.napplet.shell.supports('perm:popups')) { /* ... */ }Namespace Import
import * as napplet from '@napplet/sdk' produces an object structurally identical to window.napplet:
import * as napplet from '@napplet/sdk';
napplet.relay.subscribe({ kinds: [1] }, (e) => console.log(e));
napplet.storage.setItem('key', 'value');
napplet.config.subscribe((v) => console.log(v));Types
All protocol types are re-exported from @napplet/core and the NUB packages:
import type {
// Protocol types (from @napplet/core)
NostrEvent,
NostrFilter,
Subscription,
EventTemplate,
NappletMessage,
NubDomain,
NamespacedCapability,
ShellSupports,
// NUB message types (re-exported from NUB packages)
RelayNubMessage,
IdentityNubMessage,
StorageNubMessage,
IfcNubMessage,
KeysNubMessage,
Action,
} from '@napplet/sdk';Core Protocol Types
| Type | Description |
|------|-------------|
| NostrEvent | Standard Nostr event object |
| NostrFilter | Relay subscription filter |
| Subscription | Handle with close() method |
| EventTemplate | Unsigned event for relay.publish() |
| NappletMessage | Base JSON envelope type for all protocol messages |
| NubDomain | String literal union of NUB domain names |
| NamespacedCapability | Union of NubDomain \| nub:* \| perm:* for supports() |
| ShellSupports | Interface for the shell capability query API |
NUB Message Types
These are discriminated union types covering all messages in each NUB domain. Useful for writing typed message handlers in shell implementations or protocol-aware code.
| Type | NUB Package | Description |
|------|-------------|-------------|
| RelayNubMessage | @napplet/nub-relay | Discriminated union of all relay domain messages |
| IdentityNubMessage | @napplet/nub-identity | Discriminated union of all identity domain messages |
| StorageNubMessage | @napplet/nub-storage | Discriminated union of all storage domain messages |
| IfcNubMessage | @napplet/nub-ifc | Discriminated union of all IFC domain messages |
| KeysNubMessage | @napplet/nub-keys | Discriminated union of all keys domain messages |
| MediaNubMessage | @napplet/nub-media | Discriminated union of all media domain messages |
| NotifyNubMessage | @napplet/nub-notify | Discriminated union of all notify domain messages |
| ConfigNubMessage | @napplet/nub-config | Discriminated union of all config domain messages |
Individual message types (e.g., RelaySubscribeMessage, IdentityGetPublicKeyMessage) are also re-exported from
@napplet/sdk for fine-grained typing.
NUB Domain Constants
Each NUB domain has a string constant re-exported from its package:
import { RELAY_DOMAIN, IDENTITY_DOMAIN, STORAGE_DOMAIN, IFC_DOMAIN, THEME_DOMAIN, KEYS_DOMAIN, MEDIA_DOMAIN, NOTIFY_DOMAIN, CONFIG_DOMAIN } from '@napplet/sdk';
// Values: 'relay', 'identity', 'storage', 'ifc', 'theme', 'keys', 'media', 'notify', 'config'These constants are re-exported from the individual NUB packages. Use them with the shell capability query API for type-safe conditional logic:
if (window.napplet.shell.supports('nub:relay')) {
// relay operations are available
}
if (window.napplet.shell.supports('nub:identity')) {
// identity queries are available
}
if (window.napplet.shell.supports('nub:config')) {
// NUB-CONFIG is available -- schema registration and subscribe()
}Runtime Guard
If window.napplet is not installed when an SDK method is called, a clear error is thrown:
Error: window.napplet not installed -- import @napplet/shim firstThis protects against importing @napplet/sdk without the side-effect shim import.
SDK vs Shim
| | @napplet/sdk | @napplet/shim |
|---|---|---|
| Import style | import { relay } from '@napplet/sdk' | import '@napplet/shim' (side-effect) |
| What it does | Named exports wrapping window.napplet | Installs window.napplet + shell registration |
| Dependencies | @napplet/core (types only) | None (types from @napplet/core) |
| Side effects | None | Yes -- installs globals, registers with shell |
| Required | Optional convenience | Required in every napplet |
Typical usage: Import both -- shim for runtime, SDK for developer API:
import '@napplet/shim'; // required: installs window.napplet
import { relay, ipc, storage, keys, media, notify } from '@napplet/sdk'; // optional: typed APIIf you are writing a vanilla napplet with no build step, use window.napplet.* directly after importing the shim -- the SDK is not required.
Protocol Reference
- NIP-5D -- Napplet-shell protocol specification
- @napplet/shim -- Window installer package
- @napplet/core -- Shared protocol types
License
MIT
