@napplet/core
v0.31.1
Published
Shared protocol types, constants, and message definitions for the napplet protocol
Downloads
2,336
Maintainers
Readme
@napplet/core
JSON envelope types and NAP dispatch infrastructure for the napplet ecosystem.
Getting Started
Package Overview
This package is the single source of truth for all protocol-level definitions in the napplet ecosystem. All other @napplet/* packages import their envelope types, dispatch infrastructure, and protocol constants from here.
Zero dependencies. No DOM or browser APIs. Works in any JavaScript runtime.
Installation
npm install @napplet/coreQuick Start
import {
type NappletMessage, type NapDomain,
type NapHandler, type NapDispatch,
NAP_DOMAINS, SHELL_BRIDGE_URI, PROTOCOL_VERSION,
createDispatch, registerNap, dispatch, getRegisteredDomains,
ALL_CAPABILITIES, TOPICS,
} from '@napplet/core';API Reference
Envelope Types
The JSON envelope wire format is the primary API introduced in NIP-5D v4. All messages between napplet and shell use a type field as a discriminant in domain.action format.
NappletMessage
Base interface for all messages exchanged between napplet and shell.
interface NappletMessage {
/** Message type discriminant in "domain.action" format */
type: string;
}Concrete message types extend this interface with domain-specific payload fields:
// Example concrete message type:
interface RelaySubscribe extends NappletMessage {
type: 'relay.subscribe';
id: string;
subId: string;
filters: NostrFilter[];
}The type field domain prefix (relay, identity, storage, inc, theme, keys, media, notify, config, resource, cvm, outbox, upload, intent, ble, webrtc, link, count, lists, serial, common, dm) routes messages to the correct NAP handler via dispatch().
NapDomain
String literal union of the NAP capability domains.
type NapDomain = 'relay' | 'identity' | 'storage' | 'inc' | 'theme' | 'keys' | 'media' | 'notify' | 'config' | 'resource' | 'cvm' | 'outbox' | 'upload' | 'intent' | 'ble' | 'webrtc' | 'link' | 'count' | 'lists' | 'serial' | 'common' | 'dm';| Domain | Scope |
|-----------|------------------------------------------|
| relay | Relay proxy (subscribe, publish, query) |
| identity | Read-only user identity queries |
| storage | Scoped key-value storage proxy |
| inc | Inter-napplet communication (dispatch + channel) |
| theme | Theme tokens and appearance settings |
| keys | Keyboard forwarding and action keybindings|
| media | Ownership-aware media session control |
| notify | Shell-rendered notifications |
| config | Per-napplet declarative configuration (JSON Schema-driven) |
| resource | Byte-fetching primitive (URL to Blob) |
| cvm | Native ContextVM / MCP-over-Nostr bridge |
| outbox | Outbox-aware relay routing |
| upload | Shell-mediated file/blob upload |
| intent | Archetype intent dispatch |
| ble | Runtime-mediated Bluetooth LE/GATT sessions |
| webrtc | Runtime-mediated WebRTC data sessions |
| link | Shell-mediated external link opening |
| count | Runtime-mediated event counts |
| lists | Runtime-mediated NIP-51 list mutations |
| serial | Runtime-mediated serial device access |
| common | Shell-mediated common social actions |
| dm | Runtime-mediated direct messages |
NAP_DOMAINS
Runtime constant array of all NAP domain strings. Useful for iteration and validation.
const NAP_DOMAINS: readonly NapDomain[] = ['relay', 'identity', 'storage', 'inc', 'theme', 'keys', 'media', 'notify', 'config', 'resource', 'cvm', 'outbox', 'upload', 'intent', 'ble', 'webrtc', 'link', 'count', 'lists', 'serial', 'common', 'dm'];
for (const domain of NAP_DOMAINS) {
console.log(`Checking support for: ${domain}`);
}NappletGlobal
Type for the runtime-injected window.napplet namespace. Domain properties are optional: presence means the runtime exposes that NAP, absence means it is unavailable.
if (window.napplet?.relay) {
await window.napplet.relay.query([{ kinds: [1], limit: 1 }]);
}IntentApi
NAP-INTENT targets an archetype rather than a specific running surface. invoke(request) accepts the canonical IntentRequest; open(archetype, payload?, opts?) is action-"open" sugar. Archetype and optional convention are orthogonal.
const result = await window.napplet.intent?.open(
'profile',
{ pubkey: 'abc123' },
{ convention: 'napplet:profile/open', behavior: { newWindow: true } },
);
if (result?.handled) {
console.log(`Handled by ${result.handler} in ${result.windowId}`);
} else {
throw new Error(result?.error ?? 'intent not handled');
}Results include required ok, archetype, action, and handled fields. IntentBehavior includes focus, newWindow, and reuse; availability candidates expose supported actions and conventions. See the living NAP-INTENT document.
NAP Dispatch Infrastructure
The dispatch system allows NAP modules to self-register at import time. Inbound messages are routed to the correct NAP handler based on the domain prefix extracted from message.type (the part before the first .). The exported helper names (registerNap, NapHandler, NapDispatch) are retained for package compatibility.
createDispatch()
Factory that returns an isolated { registerNap, dispatch, getRegisteredDomains } backed by its own Map<string, NapHandler>. Use for testing or multi-instance scenarios.
function createDispatch(): NapDispatch;import { createDispatch } from '@napplet/core';
const { registerNap, dispatch } = createDispatch();
registerNap('relay', handleRelayMessage);
dispatch({ type: 'relay.subscribe' }); // trueregisterNap(domain, handler)
Register a handler for a NAP domain on the module-level singleton registry. NAP modules call this at import time.
const registerNap: (domain: string, handler: NapHandler) => void;import { registerNap } from '@napplet/core';
registerNap('relay', (msg) => {
// handles all relay.* messages
console.log('relay message:', msg.type);
});Throws if the domain is already registered.
dispatch(message)
Dispatch a message to the handler matching its domain prefix. Returns true if a handler was found and called.
const dispatch: (message: NappletMessage) => boolean;import { dispatch } from '@napplet/core';
dispatch({ type: 'relay.subscribe' }); // true (if relay handler registered)
dispatch({ type: 'unknown.action' }); // false
dispatch({ type: 'malformed' }); // false (no dot)The domain is extracted by splitting message.type on the first .. A type with no . or an empty domain prefix returns false without throwing.
getRegisteredDomains()
Return all currently registered domain strings from the singleton registry.
const getRegisteredDomains: () => string[];import { getRegisteredDomains } from '@napplet/core';
getRegisteredDomains(); // ['relay', 'identity', 'storage']NapHandler
Callback type for NAP message handlers.
type NapHandler = (message: NappletMessage) => void;NapDispatch
Interface returned by createDispatch().
interface NapDispatch {
registerNap: (domain: string, handler: NapHandler) => void;
dispatch: (message: NappletMessage) => boolean;
getRegisteredDomains: () => string[];
}Protocol Types
Types shared by all napplet packages for Nostr event structures and the capability system.
NostrEvent
Standard Nostr event structure (used by relay NAP and identity NAP).
interface NostrEvent {
id: string;
pubkey: string;
created_at: number;
kind: number;
tags: string[][];
content: string;
sig: string;
}NostrFilter
Subscription filter (used by relay NAP for relay.subscribe and relay.query).
interface NostrFilter {
ids?: string[];
authors?: string[];
kinds?: number[];
since?: number;
until?: number;
limit?: number;
[key: `#${string}`]: string[] | undefined; // tag filters, e.g. '#t', '#e'
}Capability
String union type listing all 10 protocol capability strings.
type Capability =
| 'relay:read' | 'relay:write'
| 'cache:read' | 'cache:write'
| 'hotkey:forward'
| 'sign:event' | 'sign:nip04' | 'sign:nip44'
| 'state:read' | 'state:write';Shell implementations use bitfield constants (CAP_*) for fast runtime checks. Capability strings are the human-readable protocol-level representation.
ALL_CAPABILITIES
readonly Capability[] containing all 10 capability strings.
for (const cap of ALL_CAPABILITIES) {
console.log(cap); // 'relay:read', 'relay:write', ...
}Subscription
Handle returned by relay.subscribe() and inc.on().
interface Subscription {
close(): void;
}EventTemplate
Unsigned event template passed to relay.publish().
interface EventTemplate {
kind: number;
content: string;
tags: string[][];
created_at: number;
}Protocol Constants
| Constant | Value | Description |
|----------|-------|-------------|
| PROTOCOL_VERSION | '4.0.0' | Current napplet-shell protocol version (JSON envelope era, NIP-5D v4) |
| SHELL_BRIDGE_URI | 'napplet://shell' | URI identifying the shell bridge in relay tags |
| REPLAY_WINDOW_SECONDS | 30 | Maximum event age (seconds) for replay protection |
Topic Constants
The TOPICS object contains string constants for INC topic-based routing. Its archetype-open entries use the current advisory convention names; topic strings are passed directly in inc.emit and inc.subscribe payloads.
import { TOPICS } from '@napplet/core';
TOPICS.NOTE_OPEN // 'napplet:note/open'
TOPICS.PROFILE_OPEN // 'napplet:profile/open'
TOPICS.DM_OPEN // 'napplet:dm/open'
TOPICS.WM_FOCUSED_WINDOW_CHANGED // 'wm:focused-window-changed'
// ... see source for full listConvention topic strings are opaque and route only when the sender and subscriber use the same string. They do not define a payload schema, Nostr event kind, query, wildcard, prefix, or canonicalization rule; receivers validate any local payload choice themselves.
Types
import type {
NappletMessage, NapDomain,
NapHandler, NapDispatch,
NostrEvent, NostrFilter, Capability,
Subscription, EventTemplate, NappletGlobal,
} from '@napplet/core';| Type | Description |
|------|-------------|
| NappletMessage | Base interface for all JSON envelope messages |
| NapDomain | Union of the active NAP domain strings |
| NapHandler | Callback type for domain handlers |
| NapDispatch | Interface returned by createDispatch() |
| NostrEvent | Nostr event structure |
| NostrFilter | Subscription filter for relay NAP |
| Capability | Human-readable capability string union |
| Subscription | Handle with close() returned by subscribe/on |
| EventTemplate | Unsigned event template for publishing |
Boundary Helpers (clone-safety)
NAP shims cross the napplet ⇄ shell boundary by structured-cloning a JSON envelope through postMessage. Framework reactive values — Svelte 5 $state, Vue reactive, Solid stores — are Proxy objects that are not structured- cloneable, so a naive postMessage throws DataCloneError, which gets silently swallowed in async paths (the envelope never crosses the boundary). These helpers make that loud or transparent.
| Export | Description |
|--------|-------------|
| sendEnvelope(target, message, targetOrigin?) | The single boundary chokepoint shims post through. Per the active mode it posts as-is, snapshot-recovers a non-cloneable arg, or throws a loud, synchronous, actionable error. |
| toCloneableSnapshot(value) | Deep snapshot stripping reactive proxies into plain objects/arrays while preserving binary (Uint8Array/ArrayBuffer), Date, RegExp, Map, Set, and cycles. Lossless for binary (unlike JSON); functions/symbols throw. |
| setCloneMode(mode) / getCloneMode() | 'auto' (default: post as-is, snapshot-and-retry on DataCloneError, warn once), 'strict' (throw, never recover), or 'snapshot' (eagerly snapshot every envelope). |
| clearCloneWarnings() | Reset the once-per-type auto-recovery warnings. |
import { setCloneMode, toCloneableSnapshot } from '@napplet/core';
// Default 'auto' handles reactive state transparently on the failure path.
// Or normalize explicitly:
napplet.outbox.subscribe(toCloneableSnapshot(filters), { relays });
// Or globally:
setCloneMode('snapshot');These are SDK plumbing only — identical plain envelopes reach the wire, so they add no protocol surface.
Integration Note
@napplet/core is consumed by all packages in the napplet ecosystem for envelope types and NAP dispatch.
- In this repo:
@napplet/shim,@napplet/sdk, and@napplet/vite-pluginimportNappletMessage,NapDomain, and all shared protocol types from@napplet/core. @napplet/napdomain modules (@napplet/nap/relay,@napplet/nap/identity,@napplet/nap/storage,@napplet/nap/inc,@napplet/nap/keys,@napplet/nap/media,@napplet/nap/notify,@napplet/nap/config,@napplet/nap/link,@napplet/nap/lists, and other active domain subpaths): extendNappletMessagefor their domain-specific message types and callregisterNapat import time.
Protocol Reference
- NIP-5D -- Napplet-shell protocol specification
License
MIT
