NAP-WEBRTC / PR #59
Runtime-Mediated WebRTC Signaling
Full specification from the fetched upstream revision ↗. The document below is rendered from its original Markdown. Follow the latest source on GitHub ↗.
NAP-WEBRTC
Runtime-Mediated WebRTC Signaling
draft
NAP ID: NAP-WEBRTC
Domain: webrtc
Depends: none.
Web binding (NIP-5D): window.napplet.webrtc; domain presence signals availability.
Description
NAP-WEBRTC lets a napplet ask the shell to establish a WebRTC peer session whose
signaling is carried over Nostr. The shell owns the signaling mechanism, relay
subscriptions, signing, encryption, SDP offers and answers, ICE candidates, and
RTCPeerConnection lifecycle. The napplet names the peer or room it wants to
join and sends or receives opaque application payloads once the session is open.
This is a transport capability, not a call protocol. It does not standardize voice, video, mute state, chat messages, or application commands. Those semantics belong in a NAP-N or an application protocol carried over the established channel.
API Surface
| Operation | Parameters | Result | Wire |
|---|---|---|---|
open |
request (WebrtcOpenRequest) |
WebrtcOpenResult |
webrtc.open / webrtc.open.result |
send |
sessionId (tstr), payload (any) |
none | webrtc.send / webrtc.send.result |
close |
sessionId (tstr), optional reason (tstr) |
none | webrtc.close / webrtc.close.result |
onEvent |
handler for WebrtcEvent |
Subscription handle |
webrtc.event |
Schemas
Enums:
| Name | Values |
|---|---|
WebrtcState |
connecting, open, closed |
WebrtcPeerState |
joined, left |
WebrtcScope variants:
| Variant | Required fields | Optional fields |
|---|---|---|
| direct | type: "direct", pubkey: tstr |
none |
| room | type: "room", room: tstr |
peers: list of tstr |
WebrtcOpenRequest fields:
| Field | Required | Type |
|---|---|---|
scope |
yes | WebrtcScope |
channel |
no | text |
protocol |
no | text |
WebrtcOpenResult fields:
| Field | Required | Type |
|---|---|---|
session |
yes | WebrtcSession |
WebrtcSession fields:
| Field | Required | Type |
|---|---|---|
id |
yes | text |
scope |
yes | WebrtcScope |
channel |
yes | text |
protocol |
no | text |
state |
yes | WebrtcState |
WebrtcEvent variants:
| Variant | Required fields | Optional fields |
|---|---|---|
| state | type: "state", sessionId: tstr, state: WebrtcState |
none |
| peer | type: "peer", sessionId: tstr, pubkey: tstr, state: WebrtcPeerState |
none |
| message | type: "message", sessionId: tstr, from: tstr, payload: any |
none |
| closed | type: "closed", sessionId: tstr |
reason: tstr |
open(request) — Requests a WebRTC session. For direct, the shell connects
to one peer pubkey. For room, the shell announces presence to the room and may
connect to discovered peers or to the requested peers subset. channel names
the WebRTC data channel; when omitted, the shell chooses a default. protocol
is an application-level label for the payloads carried after connection and has
no NAP-WEBRTC semantics.
send(sessionId, payload) — Sends an opaque payload over the established
session. The shell serializes and delivers it over the WebRTC data channel. This
method does not publish the payload to Nostr.
close(sessionId, reason?) — Closes the session and asks the shell to publish
any corresponding disconnect signal where appropriate.
onEvent(handler) — Registers for shell-pushed session events. Message
payloads are untrusted input from peers.
Wire Protocol
webrtc.* messages use the NIP-5D wire format
({ "type": "domain.action", ...payload }).
| Type | Direction | Payload fields |
|---|---|---|
webrtc.open |
napplet -> shell | id, request |
webrtc.open.result |
shell -> napplet | id, session?, error? |
webrtc.send |
napplet -> shell | id, sessionId, payload |
webrtc.send.result |
shell -> napplet | id, error? |
webrtc.close |
napplet -> shell | id, sessionId, reason? |
webrtc.close.result |
shell -> napplet | id, error? |
webrtc.event |
shell -> napplet | event |
Key design notes:
- Request/result pairs use
idfor correlation. webrtc.eventhas noid; it is shell-initiated delivery.- Napplet-facing messages never carry raw SDP, ICE candidates, or raw signaling payloads.
- The shell may deliver
webrtc.eventstate updates beforeopen()resolves if the underlying connection progresses quickly, but it MUST NOT deliver messages for an unknownsessionId.
Examples
Open a direct peer channel:
-> {
"type": "webrtc.open",
"id": "w1",
"request": {
"scope": { "type": "direct", "pubkey": "abc123..." },
"channel": "chat",
"protocol": "chat:live"
}
}
<- {
"type": "webrtc.open.result",
"id": "w1",
"session": {
"id": "sess-1",
"scope": { "type": "direct", "pubkey": "abc123..." },
"channel": "chat",
"protocol": "chat:live",
"state": "connecting"
}
}
<- { "type": "webrtc.event", "event": { "type": "state", "sessionId": "sess-1", "state": "open" } }
Send an application payload:
-> {
"type": "webrtc.send",
"id": "s1",
"sessionId": "sess-1",
"payload": { "body": "hello" }
}
<- { "type": "webrtc.send.result", "id": "s1" }
Receive a peer payload:
<- {
"type": "webrtc.event",
"event": {
"type": "message",
"sessionId": "sess-1",
"from": "abc123...",
"payload": { "body": "hi" }
}
}
Signaling
NIP-100 is an unmerged draft at https://github.com/nostr-protocol/nips/pull/363. It is a useful reference for WebRTC signaling over Nostr, but it is not part of the NAP-WEBRTC interface.
A conformant shell MAY use NIP-100, a later accepted Nostr signaling NIP, or
another Nostr signaling scheme. That choice is a runtime detail. The napplet
observes only the webrtc.* interface, session state, peer state, and opaque
peer messages defined here.
Regardless of signaling scheme, the shell MUST treat raw signaling payloads as shell-internal transport data, not as napplet-visible API data.
Error Handling
Any result message MAY include an error field (string). When error is
present, other result fields are undefined.
Common errors include "unsupported scope", "signaling unavailable",
"peer unavailable", "session not found", "session closed",
"payload too large", and "policy denied".
The shell SHOULD send a webrtc.event with type: "closed" when an established
session ends because a peer disconnects, ICE fails, signaling expires, or policy
revokes the capability.
Shell Behavior
- The shell MUST respond to every request with a result message carrying the same
id. - The shell MUST NOT expose raw relay sockets, signing keys, SDP descriptions,
ICE candidates, or
RTCPeerConnectionobjects to napplets through this NAP. - The shell MUST perform any signaling signing and encryption itself. It MUST NOT delegate keys or raw signing material to the napplet.
- The shell MUST scope sessions per napplet and MUST NOT deliver one napplet's peer messages to another napplet.
- The shell MUST enforce per-napplet policy before opening a session, publishing signaling events, or delivering peer messages.
- The shell MAY limit relays, peers, rooms, payload sizes, data-channel labels, connection duration, or concurrent session count.
- The shell MAY route a room session through a WebRTC media server or peer mesh. That topology is shell policy and is not visible to the napplet.
- The shell SHOULD publish
disconnectwhenclose()succeeds or when it can gracefully tear down a session.
Security Considerations
- WebRTC can reveal network metadata to peers. Shells MUST treat
open()as an untrusted request and SHOULD require user policy or consent before starting a session. - Signaling room identifiers can be sensitive. Shells SHOULD prefer random, non-meaningful room ids and SHOULD avoid exposing private room ids to napplets that did not create or join them.
- Peer payloads are untrusted. Napplets MUST validate every
messagepayload against their own application protocol before acting on it. - The shell owns encryption and signing. Napplets MUST NOT receive private keys, raw signaling payloads, or decrypted signaling secrets through this interface.
- The shell SHOULD rate-limit
open()andsend()to prevent relay spam, connection floods, and peer abuse. - A runtime that supports media streams through a future extension MUST keep camera, microphone, and screen capture under user control. NAP-WEBRTC alone does not grant media capture authority.
Implementations
- (none yet)
Changelog
1a83117- Introduced NAP-WEBRTC as a runtime-owned WebRTC signaling surface.394b597- Adopted injected-domain availability and linked the current upstream web binding.