Note: This specification is under active development and therefore incomplete. If you’re looking for an overview of the proposal, please refer to the Explainer.
1. Introduction
This section is non-normative.
The Local Peer-to-Peer API aims to give browsers the means to communicate directly, without the aid of a server in the middle. It is designed to enable this communication within the confines of a local communication medium.
Many modern Web security measures rely on the presence of naming, signaling and certificate authorities. Local use-cases where these authorities are not readily available have started lagging behind in user experience or are not supported altogether. The Local Peer-to-Peer API aims to bring back first-class support for local communication use-cases while working within the same strict user-friendliness, security and privacy requirements.
Examples of potential uses of this API include: Collaboration tools that work during an internet outage or emergency situations, connecting to your NAS, your home security system, your robotic assistant doing the dishes or your GPU farm in the basement that’s running your personalized virtual assistant. See also Use Cases.
This specification aims to strike a balance between creating a powerful new building block for developers and providing a seamless, secure and privacy preserving experience for browser users. As an example: while the API doesn’t provide raw socket access, it does aim to give developers the flexibility to innovate on top by providing a persistent, two-way communication channel with little overhead.
The API is designed to be backed by an authenticated, streams-based transport. It is defined against the abstract operations of § 6 Transport abstraction, which a transport binding maps onto a concrete protocol. As a commitment to an open standards-based implementation path, this specification defines a binding to the Open Screen Network Protocol in § 15 Binding to the Open Screen Network Protocol. Other bindings are anticipated, for example WebTransport over QUIC with peers authenticated on the local network as explored by the IETF SETTLE working group.
2. Terminology
The term local communication medium refers to a communication medium that provides a local, direct communication channel between the communicating devices. It carries IP between the devices, directly or through a local network, for example a wired or wireless local area network or Wi-Fi Direct. Discovery and authentication may also use other channels, such as Bluetooth Low Energy, NFC or a QR code. This spec will not mandate which underlying technologies must be supported. Being on it is not a security property; see § 14.5 Authentication and locality.
The term local communication refers to data communication over the local communication medium.
The term communication medium topology refers to the composition of the local communication medium which may include sensitive information such as IP or other address information or the number of devices connected to the medium.
3. Permission Policy Integration
The Local Peer-to-Peer API defines a policy-controlled feature identified by the token "local-peer-to-peer". Its default allowlist is "self".
Workers (dedicated and shared) adhere to the permission policy set by their owning document(s):
- Dedicated workers can be created from other workers, in which case the permission policy of the first owning document (in case of a dedicated worker) or owning documents (in case of a shared worker) up the owner chain will be used.
- Shared workers often have multiple owning documents as they can be obtained by other documents with the same origin. In this case, all owning documents must be allowed to use the policy-controlled feature defined by this specification.
Note: There has been discussion on allowing setting permission policy directly on a worker on creation, in which case that would have to be consulted as well.
The user interface that a global object global needs, such as the picker or a prompt, is shown for its user interface document, and the user’s consent there is consent for global’s relevant settings object’s origin. For a Window, it is its associated Document. For a dedicated worker, it is that of its owner, up the owner chain. For a shared worker, it is one of its owning documents that is fully active and whose visibility state is "visible", chosen by the user agent. If there is none, or it is not fully active, the user agent treats the user interface as dismissed: start() and start() reject with a "NotAllowedError" DOMException, and an incoming session that needs a prompt is refused.
Decide whether to expose this API in service workers, which have no document to show user interface or to own consent.
The default allowlist of "self" allows usage in same-origin nested frames but prevents third-party content from using the feature. Third-party content usage can be selectively enabled by adding allow="local-peer-to-peer" attribute to the frame container element:
< iframe src= "https://third-party.com" allow= "local-peer-to-peer" />< /iframe>
Alternatively, the Local Peer-to-Peer API can be disabled completely by specifying the permissions policy in a HTTP response header:
Permissions- Policy: local- peer- to- peer= ()
See [PERMISSIONS-POLICY] for more details.
4. Peer Management
Note: This section and its subsections use RFC 2119 terminology in a relaxed manner. These sections will be converted into well-defined algorithmic normative prose informed by further implementation experience. For now, this relaxed description better allows for rapid prototyping.
The user agent is in charge of managing peers.
A peer is an equal participant in the local communication. These nodes can communicate without the need for a central coordination by a server.
A user agent has an associated local peer-to-peer manager in charge of managing peers. Its responsibility is to start local peer discovery, establish sessions and acquire a local peer grant on a per-origin basis. This is done to avoid exposing information about the communication medium topology.
4.1. Local state
The local peer-to-peer manager has:
- local trust store
-
A set of trust anchors, see § 4.5 Peer authentication.
- known peers map
-
An ordered map from peer identity to known peer: a peer the user agent has had an authenticated session with, which the user selected or accepted.
- peer grants
-
A set of tuples of an origin, a peer identity that the origin may use, and a requesting origin. The requesting origin is "
selected" for a peer the user selected in acquire a local peer grant. For a peer whose incoming session the user accepted, it is the session’s requesting origin, possibly null, so the grant covers only sessions requested on behalf of that origin, or with no origin. An origin has a peer grant for a peer identity if the peer grants contain a tuple with both.
A known peer records the anchor key and subject it last chained to on an authenticated session, or null. Whether it is authenticated is not stored: without a live session, it is evaluated on these, against the local trust store. So removing a trust anchor can tell which peers are no longer authenticated.
The state is changed only by the operations below. How it is stored is implementation-defined.
Other specifications can define trust anchor checks: steps that, given a trust anchor, return whether it may be added to the local trust store. Local HTTPS uses one to keep at most one key per host.
-
Let anchor be a trust anchor with key, scope and, if given, subject.
-
If the local trust store contains a vouching trust anchor with key, or the same one peer trust anchor, return success.
-
If any trust anchor check returns false given anchor, return failure.
-
If scope is vouching, remove from the local trust store the one peer trust anchors with key, which it covers.
-
Append anchor to the local trust store and return success.
-
Remove anchor from the local trust store.
-
For each identity → peer of a clone of the known peers map, if peer is no longer authenticated, forget a known peer given identity. This revokes its peer grants and closes its sessions.
-
For each trust anchor in the local trust store whose key is old, set its key to new.
-
For each known peer whose anchor key is old, set it to new.
-
Set the known peers map[peer’s peer identity] to peer, recording as its anchor key and subject the ones it chains to on that session.
-
For each origin that has a peer grant for identity, revoke a peer grant given origin and identity.
-
Close the sessions with a peer given identity.
-
Remove the known peers map[identity].
selected":
-
Append (origin, identity, requester) to the peer grants.
-
Remove from the peer grants each tuple with origin and identity.
-
Close the sessions with a peer given identity and origin.
When an origin’s site data or permissions are cleared, the user agent runs revoke a peer grant given that origin and each peer identity it has a peer grant for.
-
For each
LP2PConnectionconnection whose[[Session]]is with the peer with identity, and, if origin is given, whose relevant settings object’s origin is origin: close connection.[[Session]], and queue a global task on the networking task source given connection’s relevant global object to run finalize given connection, which firesclose. -
If origin is not given, close each Local Peer-to-Peer session with the peer with identity that is not yet exposed to script: an incoming session, or one being established by start an LP2PRequest. The steps waiting on it then abort without granting, adding or delivering anything.
Note: Peer grants are origin-level while the local trust store is shared by all origins, so that the user doesn’t need to authenticate the same peer again for every origin. Origins are kept apart by the peer grants and by the requesting origin of each session. Peer grants could be stored as the origin’s permission data in the permission store, as Web Bluetooth does for its allowed devices.
Decide whether to back peer grants with the permission store, as the extra permission data of a "local-peer-to-peer" powerful feature, which would also give a permission query and revocation.
Validate that a local trust store shared by all origins is appropriate, or whether trust anchors should be partitioned, for example per top-level site. [WICG/local-peer-to-peer Issue #24]
The user agent may persist its local trust store, known peers and/or peer grants. If the user agent chooses to do so, it must do so in accordance with the persistent state rules of the transport binding in use. Such state is said to be persisted. Results of discover peers, such as addresses, are not part of this state.
When the user clears all browsing data, the user agent clears its local trust store, known peers map and peer grants, and replaces its own credentials. Each user profile and each private browsing session has its own.
4.2. Trust lifecycle
Trust relationships are visible, reviewable and removable: the user agent must let the user view the trust anchors, known peers and peer grants, and remove them, with remove a trust anchor, forget a known peer and revoke a peer grant. Removal revokes: it closes the sessions that relied on what was removed, including sessions still being set up. Removing a peer in the user interface runs forget a known peer and also removes the one peer trust anchor for it, so that it is no longer authenticated. User agents should bound trust over time, for example by letting trust anchors or peer grants expire, or by asking the user to review those not used for a long period.
Note: No online authority can revoke a peer. Removal by the user, and trust bounded in time, take its place. A vouching party also bounds the validity of the credentials it vouches for, see § 5 Vouching and delegated trust.
4.3. Peer advertisement
The user agent can make itself discoverable by advertising, using start advertising.
Note: The user agent must get the user’s explicit consent in order to start advertising.
When advertising, the user agent must listen for incoming sessions. The user agent must not directly provide access to an incoming session to any origin. Instead, unless the origin that initiated peer advertisement already has a peer grant for the peer and the session’s requesting origin, the user agent must prompt to grant peer access to that origin. Accepting the incoming session runs grant a peer for both, so later sessions from the peer on behalf of the same requesting origin do not prompt again; only then does the user agent run add a known peer with the peer. See § 7.1 Incoming sessions.
The user agent must deliver an incoming session only to the LP2PReceiver whose advertisement it was reported for, if that receiver is started and allows the session’s requesting origin, and refuse the session otherwise. It never delivers the session to another receiver instead. A refusal is indistinguishable, to the client, from the user declining. An LP2PReceiver allows an origin origin, an origin or null, if its [[AllowAnyRequester]] is true, or if origin is neither null nor an opaque origin, and is same origin with the receiver’s relevant settings object’s origin or with an item of its [[AllowedOrigins]].
4.4. Peer discovery
When asked to start local peer discovery, the user agent runs discover peers.
Discovered peers and their advertisements are only shown in the picker, see acquire a local peer grant; they are not known peers, whose entries are added or updated only from authenticated sessions, by add a known peer. The user agent must never expose the full result of peer discovery with an origin. Instead, the user agent must acquire a local peer grant to grant access to a peer for the origin that initiated the peer request.
4.5. Peer authentication
A trust anchor is a key that the user agent trusts to authenticate peers. It has:
- key
-
The key that peers' credentials chain to. What the credentials are, and how they chain to a key, is defined by the transport binding.
- scope
-
Either one peer, a single peer that the anchor vouches for, or vouching, any peer the anchor vouches for, within the limits of § 5 Vouching and delegated trust.
A trust anchor is added to the local trust store only by a trust ceremony: a run of authenticate a session in which the user takes part, for example by entering a PIN or scanning a QR code. A trust ceremony with a peer runs add a trust anchor scoped to one peer: that peer, unless the user chooses to trust a vouching party for its devices, see § 5 Vouching and delegated trust. If add a trust anchor returns failure, the ceremony fails.
A peer that uses its own key is self-anchored: its trust anchor is its key, scoped to one peer, that peer.
On the server, a trust anchor that a trust ceremony added is kept only if the user accepts the session; otherwise it is removed, see § 7.1 Incoming sessions. On the client, it is kept even if the server then refuses the session: the user chose to pair with the device, and a refusal concerns the requesting origin, not trust.
A peer is said to be authenticated when its credentials chain to the key of a trust anchor in the local trust store, and the peer is within that anchor’s scope.
A peer whose claimed peer identity is that of a known peer, but whose credentials do not chain to that peer’s anchor key, is not that known peer. The user agent must not mark it as known, and must warn the user that it may not be the device they know before running a trust ceremony with it.
A peer’s credentials may change over time without changing its peer identity, as long as they chain to the same key. Whether a key can be replaced with continuity is defined by the transport binding; if so, the user agent runs replace a trust anchor key. Otherwise, a new key needs a new trust ceremony.
Define how a peer can move to another trust anchor’s key while keeping its peer identity and peer grants, for example by proving that it holds both keys.
Note: Trust anchors may also be used to validate HTTPS servers on the local communication medium, each constrained to a namespace of local names; see the separate Local HTTPS draft.
4.6. Peer grant
The user agent, with the user’s consent, must acquire a local peer grant for an origin to get access to a peer.
Note: The user agent must get the user’s explicit consent in order to grant an origin access to a peer. For enhanced privacy protection, the user agent must provide means to dismiss any related user interface and this action must not be detectable by script or have script-observable side-effects. The user interface may provide means to revoke the grant or to make the grant persisted.
When asked to acquire a local peer grant for an origin, the user agent displays the advertisements that discover peers reports to the user through its native user interface, each with its nickname and its peer, and returns the advertisement the user selects, or null if the user dismisses the user interface. It marks a peer as known if it claims the peer identity of a known peer, and a peer that claims to be vouched for as new, with the name of the vouching party; these claims are only verified once a session with the peer is authenticated. The user agent may leave out peers whose capabilities lack the features the page needs. Selecting an advertisement is the user’s consent. Once a session with its peer is authenticated as the peer the user selected and accepted, the user agent runs grant a peer for the origin. See start().
5. Vouching and delegated trust
A vouching party, such as a home hub, issues credentials to other peers, so that the user does not need a trust ceremony with each of them. Whether a peer is a vouching party is defined by the transport binding.
The user trusts a vouching party for its devices only in a trust ceremony with the vouching party itself, as a separate choice from pairing with it as one device. The ceremony then runs add a trust anchor with the party’s key scoped to vouching, which removes the one peer trust anchors with that key; their peers keep their peer identities. Its user interface says which devices the choice covers, including those already paired, and that removing it removes trust in all of them.
A peer whose credentials chain to a vouching trust anchor is authenticated: its peer identity and the display name that the vouching party attests are trusted. Until the user confirms it, it is unconfirmed: the user agent must not run add a known peer with it, and Local HTTPS does not trust its hosts for navigations the user initiates. The user confirms a peer by selecting it in acquire a local peer grant, or by accepting its incoming session in § 7.1 Incoming sessions; for an unconfirmed peer, both name the vouching party. This is a single choice, such as "Connect to printer.home.local? Vouched for by Home.", not a trust ceremony.
Note: Confirmation is not verification: a vouching party can attest a misleading name. Confirmation makes each of its devices visible to the user once, so a compromised vouching party cannot silently add trusted devices; it still authenticates whatever it vouches for until the user removes it.
A transport binding that supports vouching parties must ensure that:
-
a vouching party is recognized from its credentials, not from a claim, such as one made in discovery;
-
each credential it vouches for names a subject, stable and unique under the vouching party, and a display name it attests;
-
limits on what it may vouch for can be expressed and are enforced, at least a namespace of names;
-
the credentials it vouches for have a bounded validity, so that a compromise ages out without a revocation service;
-
the user agent can tell, during authenticate a session, a peer vouched for from one anchored directly.
-
A device paired directly whose credentials are issued by a hub: the hub’s key, scoped to one peer, that device. The device’s credentials can rotate freely, and other devices of the hub are not trusted.
-
A hub the user chose to trust for its devices, through a trust ceremony with the hub: the hub’s key, scoped to vouching. It replaces the one peer anchors for that hub’s devices. A new device of the hub needs no ceremony, only the user’s confirmation on first use. Removing the anchor removes trust in all of the hub’s devices.
Note: A vouching party corresponds to the referee of REQ-ARCH-02 in the IETF SETTLE requirements: a name-constrained certificate authority for local names. The requirements above are input to that work.
6. Transport abstraction
The API and its algorithms are defined against the abstract concepts and operations in this section. A transport binding defines how a concrete protocol implements them. This specification defines one transport binding, in § 15 Binding to the Open Screen Network Protocol. Which bindings a user agent implements is implementation-defined. A peer is associated with the binding through which it was discovered or through which its session arrived, and all operations involving that peer use that binding.
A transport binding must provide end-to-end confidentiality and integrity for all communication with a peer, including when the path to the peer goes through an intermediary.
6.1. Peers and sessions
A peer has the following, provided by its transport binding. None of them is exposed to an origin.
- peer identity
-
An opaque value that identifies the peer, stable across sessions, scoped to the transport binding. It is derived from the key that the peer’s credentials chain to and the peer’s subject under it; for a self-anchored peer, the subject is its own key. It is only trusted once the peer is authenticated. The known peers map and peer grants are keyed on it.
- display name
-
A name for the peer device, for display in user agent user interface, or null. It is asserted by the peer’s user agent, not by script. Trusted only if obtained on a session that is or becomes authenticated, or by other means the user agent considers trustworthy; otherwise, such as during discover peers, it is a display hint.
- capabilities
-
The set of features the peer supports: data channels and WebTransport. Like the display name, it is asserted by the peer’s user agent and is only a hint until the peer is authenticated.
A user agent refuses an incoming data channel or incoming WebTransport session for a feature it has not enabled.
A Local Peer-to-Peer session is a confidential connection with one peer. It is authenticated if the credentials the peer presented in it chain to a trust anchor in the local trust store, which verifies the peer identity of the peer for it.
A Local Peer-to-Peer session that is exposed to an origin is not shared with other origins. A binding may carry several sessions with the same peer over one underlying connection, or use a separate connection for each.
For each Local Peer-to-Peer session, the side that established it with establish a session is the client, and the side that reported it as an incoming session is the server. Once established, the session is symmetric: either side can use any operation on it, except that:
-
the server validates the requesting origin;
-
where the binding needs it to avoid collisions, identifiers such as ids and streams follow the role;
-
only the client opens any additional underlying connection for the session.
A Local Peer-to-Peer session that is exposed to an origin has a requesting origin: the origin on whose behalf the client established it, or null if the requester has no origin, as for a native application or headless device. A user agent always conveys the origin of the page. The client conveys it inside the session, and the transport binding binds it to the authenticated session, for example by carrying it on the channel that authentication covers. The server checks it as described in § 4.3 Peer advertisement.
The requesting origin reveals which sites the user is using, and the origins that a server accepts reveal which sites its user is using. Neither is exposed to a peer that is not authenticated. A transport binding may let the server find out earlier whether it would accept the requesting origin, so that a trust ceremony is not wasted on a session it refuses, only with a mechanism that exposes neither. Otherwise, a first trust ceremony with a peer can end in a refusal.
Follow up on REQ-NAME-05, "Privacy-Preserving Discovery", of the IETF SETTLE requirements, for a mechanism that lets a server decide earlier without exposing either side’s origins.
The requesting origin is asserted by the peer’s user agent. The binding guarantees that it is consistent for the session, not that it is authentic: it does not prove that the remote page was served by that origin. User agents must not grant anything that belongs to the requesting origin, such as its permissions, storage or credentials, on the strength of it, and neither must any other implementation of a peer, such as device logic; see § 14.5 Authentication and locality. This is the same model as the Origin header in [FETCH].
Device keys, credentials and anything else that would let an origin authenticate as the user agent must not be exposed to origins. Instead, each WebTransport session has its own keying material exporter, see export keying material, so that applications can prove their origins with credentials their own origin issued.
A data channel is a reliable, ordered, message-preserving, bidirectional channel on a Local Peer-to-Peer session. Either side can open one. It has an id, an integer between 0 and 65,534 that is unique within the session, a label and a protocol. A message on a data channel is a sequence of bytes, marked as either text or binary.
6.2. Operations
A transport binding provides the following operations. Any of them can fail, including because the peer does not support it.
- To start advertising given a nickname
-
Make the user agent discoverable through discover peers on other user agents and accept sessions from peers. This returns a new advertisement, which has the given nickname, chosen by script and never verified, if the binding can carry it, and an advertisement ID: a random value, new for each advertisement, that names it to peers. Each Local Peer-to-Peer session a peer establishes with it is reported as an incoming session for that advertisement. To stop advertising given an advertisement ends both.
- To discover peers
-
Report the advertisements on the local communication medium, each with its nickname and its peer, with the peer’s display name and capabilities as far as the binding knows them, as they appear and disappear, until asked to stop discovering peers. A discovered peer identity is not verified.
- To establish a session with an advertisement given an origin
-
Establish a Local Peer-to-Peer session with the advertisement’s peer, with that origin as its requesting origin. On the peer, the session is reported as an incoming session for that advertisement, which the peer’s user agent later accepts or refuses. The client is notified of either. Refusing closes the session. The binding names the advertisement inside the session, covered by authentication, so that the session cannot be reported for another advertisement of the same peer.
- To authenticate a session
-
Mutually authenticate the two sides of a Local Peer-to-Peer session and verify the peer identity of the peer, so that the session is authenticated. If the peer’s credentials do not chain to a trust anchor in the local trust store, this is a trust ceremony, which requires user interaction on one or both devices and runs add a trust anchor as described in § 4.5 Peer authentication. Only the client initiates it; the server only responds.
- To open a data channel on an authenticated Local Peer-to-Peer session given a label, a protocol and an optional id
-
Open a data channel with that label and protocol, and with the given id or, if none is given, one chosen by the binding. The other side is notified of an incoming data channel, which its user agent accepts or refuses. The operation succeeds if it is accepted.
- To send data on a channel given a data channel and a message
-
Queue the message for delivery on the data channel. The other side is notified of a received message.
- To close a data channel
-
Close the data channel in both directions. The other side is notified that it was closed.
- To close a session
-
Close the Local Peer-to-Peer session, all of its data channels and, abruptly, all of its WebTransport sessions. The other side is notified that it was closed. The binding also reports a session as closed when the underlying transport fails.
- To open a WebTransport session over an authenticated Local Peer-to-Peer session
-
Establish a session with the same peer that provides the capabilities of a WebTransport session as defined in [webtransport], bound to the same peer identity. The other side is notified of an incoming WebTransport session, which its user agent accepts or refuses. The operation succeeds if it is accepted. The session either delivers datagrams unreliably ("
supports-unreliable") or, if the binding cannot lose messages, reliably ("reliable-only"), as WebTransport over HTTP/2 does. - To close a WebTransport session given a close code and a reason
-
Close the WebTransport session and its streams. The other side is notified that it was closed, with that code and reason. The Local Peer-to-Peer session stays open. The binding also reports a WebTransport session as closed abruptly, without a code and reason, when it fails or its Local Peer-to-Peer session closes.
- To export keying material from a WebTransport session given a label, a context and a length
-
Return keying material of that length that both sides of the WebTransport session derive identically and that no other WebTransport session yields, including one carried over the same underlying connection. The binding derives it as WebTransport over HTTP/3, Section 4.8 does: a TLS exporter [RFC9846] whose context holds an identifier of the WebTransport session, the label and the context. The requesting origin need not be part of it, since a Local Peer-to-Peer session is not shared between origins.
- To drain a WebTransport session
-
Ask the other side to finish using the WebTransport session and close it. Both sides can still use it.
Note: Data channels (LP2PDataChannel) and WebTransport sessions (LP2PQuicTransport) are two independent ways to communicate over a Local Peer-to-Peer session; neither is built on the other.
Decide whether to keep both data channels and WebTransport sessions, or converge on one. [Issue #29]
7. LP2PReceiver Interface
The LP2PReceiver interface allows advertising on the local communication medium, enabling other peers to discover and connect.
[Exposed =(Window ,DedicatedWorker ,SharedWorker ),SecureContext ]interface LP2PReceiver :EventTarget {constructor (optional LP2PReceiverOptions = {});options attribute EventHandler onconnection ;Promise <undefined >start ();undefined stop (); };
An LP2PReceiver has the following internal slots:
[[State]]-
"
initial", "advertising" or "stopped", initially "initial". [[StartPromise]]-
A
Promiseor null, initially null. [[Advertisement]]-
An advertisement or null, initially null.
[[Nickname]]-
A string.
[[AllowedOrigins]][[AllowAnyRequester]]-
A boolean, initially false.
[[ConnectionSteps]]-
Steps that take an
LP2PConnection, run in place of theconnectionevent, or null.
An LP2PReceiver is used once: after stop() it cannot be started again.
-
Set receiver.
[[ConnectionSteps]]to connectionSteps if given, or null otherwise. -
Set receiver.
[[Nickname]]to options["nickname"] if it exists, or the empty string otherwise. -
Set receiver.
[[AllowedOrigins]]to an empty list. -
For each entry of options["
allowedOrigins"]:-
If entry is "
*", set receiver.[[AllowAnyRequester]]to true and continue. -
Let url be the result of running the URL parser on entry.
-
If url is failure, throw a
TypeError. -
Append url’s origin to receiver.
[[AllowedOrigins]]. An opaque origin appended here never matches, see allows an origin.
-
-
If receiver.
[[State]]is "stopped", return a promise rejected with an "InvalidStateError"DOMException. -
If receiver.
[[StartPromise]]is not null, return it. -
Let global be receiver’s relevant global object.
-
If global is not permitted to use the "local-peer-to-peer" policy-controlled feature, as described in § 3 Permission Policy Integration, or receiver’s relevant settings object’s origin is an opaque origin, return a promise rejected with a "
NotAllowedError"DOMException. -
Let window be the relevant global object of the user interface document of global, or null if there is none. If window is null or does not have transient activation, return a promise rejected with a "
NotAllowedError"DOMException. Otherwise, consume user activation of window. -
Let promise be a new promise.
-
Set receiver.
[[StartPromise]]to promise. -
Run these steps in parallel:
-
Ask the user for permission to advertise receiver.
[[Nickname]]on the local communication medium on behalf of receiver’s relevant settings object’s origin, as described in § 4.3 Peer advertisement. -
If the user did not grant permission, queue a global task on the networking task source given global to set receiver.
[[StartPromise]]to null and reject promise with a "NotAllowedError"DOMException, and abort these steps. -
Let advertisement be the result of start advertising given receiver.
[[Nickname]]. If this fails, queue a global task on the networking task source given global to set receiver.[[StartPromise]]to null and reject promise with a "NotAllowedError"DOMException, and abort these steps. -
Queue a global task on the networking task source given global to run these steps:
-
If receiver.
[[State]]is "stopped", stop advertising given advertisement and abort these steps. -
Set receiver.
[[Advertisement]]to advertisement. -
Set receiver.
[[State]]to "advertising". -
Resolve promise with undefined.
-
-
-
Return promise.
An incoming session reported for an advertisement that start an LP2PReceiver started, before it set the receiver’s [[Advertisement]], is held until then, and refused if the receiver is stopped first.
Note: Whether the user declined to advertise or dismissed the user interface, the user agent takes the same steps, so that script cannot tell the two apart. See § 4.6 Peer grant.
-
If receiver.
[[State]]is "stopped", return. -
Set receiver.
[[State]]to "stopped". -
If receiver.
[[StartPromise]]is not null and is pending, reject it with an "AbortError"DOMException. -
If the user is being asked for permission to advertise on behalf of receiver, take down that user interface.
-
If receiver.
[[Advertisement]]is not null, stop advertising given it and set receiver.[[Advertisement]]to null.
Stopping does not affect the LP2PConnection objects already delivered; their Local Peer-to-Peer sessions stay open until closed.
7.1. Incoming sessions
-
Let added be null.
-
If session is not authenticated, wait for the client to run authenticate a session on it, and respond. This may be a trust ceremony; if it adds a trust anchor, set added to it. If authentication fails, close session and abort these steps.
-
Let decline be these steps: if added is not null, remove added; then refuse session.
-
Let receiver be the
LP2PReceiverwhose[[Advertisement]]is the advertisement session was reported for, that is, the one the client picked. If there is none, its[[State]]is not "advertising", or it does not allow the requesting origin, run decline and abort these steps. -
Let origin be receiver’s relevant settings object’s origin.
-
Let identity be the peer’s peer identity.
-
If the peer grants do not contain (origin, identity, the requesting origin):
-
Ask the user to grant the peer access to origin for sessions requested on behalf of the requesting origin. The prompt shows the peer’s display name, origin, and the requesting origin as asserted by the peer, or that the requester has no origin; for an unconfirmed peer, it also names the vouching party, and accepting confirms the peer.
-
If the user declines or dismisses the prompt, or session was closed meanwhile, run decline and abort these steps.
-
Grant a peer given origin, identity and the requesting origin.
-
-
If session was closed, abort these steps.
-
Add a known peer with the peer.
-
Accept session.
-
Queue a global task on the networking task source given receiver’s relevant global object to run these steps:
-
If receiver.
[[State]]is not "advertising" or session was closed, close session and abort these steps. -
Let connection be the result of creating an LP2PConnection given session and receiver’s relevant global object.
-
If receiver.
[[ConnectionSteps]]is not null, run them given connection and abort these steps. -
Fire an event named
connectionat receiver usingLP2PConnectionEventwith itsconnectionattribute initialized to connection.
-
7.2. LP2PReceiverOptions
dictionary {LP2PReceiverOptions DOMString ;nickname sequence <USVString >allowedOrigins = []; };
allowedOrigins, of type sequence<USVString>, defaulting to[]-
A list of URLs whose origins, in addition to the receiver’s own origin, the receiver allows as the requesting origin of an incoming session. By default, only same-origin sessions are delivered. An entry that fails to parse makes the constructor throw a
TypeError. An entry whose origin is an opaque origin, such as adata:URL, never matches. The entry "*" allows any requester, including one with no origin, such as a native application or headless device, as*does in CORS.
Decide the granularity of allowedOrigins, the requesting origin and peer grants: per origin or per site, and whether to allow wildcards such as subdomains.
7.3. LP2PConnectionEvent
In general, when defining a new interface that inherits from Event please always ask feedback from the WHATWG or the W3C WebApps WG community. See defining event interfaces.
[Exposed =(Window ,DedicatedWorker ,SharedWorker ),SecureContext ]interface :LP2PConnectionEvent Event {(constructor DOMString ,type LP2PConnectionEventInit );connectionEventInitDict readonly attribute LP2PConnection ; };connection dictionary :LP2PConnectionEventInit EventInit {required LP2PConnection ; };connection
7.4. Events
The following event is fired at the LP2PReceiver object:
| Event name | Interface | Fired when… |
|---|---|---|
connection
| LP2PConnectionEvent
| An incoming session was accepted; it is exposed as an LP2PConnection.
|
7.5. Event handlers
The following are the event handlers (and their corresponding event handler event types) that must be supported, as event handler IDL attributes, by all objects implementing the LP2PReceiver interface:
| event handler | event handler event type |
|---|---|
onconnection
| connection
|
7.6. Examples
Example: Setting up a receiver to listen for connections:
const receiver= new LP2PReceiver({ nickname: "example-receiver" , // Also accept sessions requested by this origin. allowedOrigins: [ "https://partner.example" ], }); receiver. onconnection= e=> { console. log( "Connection established!" ); const conn= e. connection; conn. onclose= () => console. log( "Connection closed." ); }; // Blocks until permission is received. await receiver. start(); // Later: stop advertising. Delivered connections stay open. receiver. stop();
8. The LP2PRequest Interface
The LP2PRequest interface represents a request for a connection to another local peer.
[Exposed =(Window ,DedicatedWorker ,SharedWorker ),SecureContext ]interface LP2PRequest {constructor (optional LP2PRequestOptions = {});options Promise <LP2PConnection >start (optional LP2PRequestStartOptions = {}); };options
An LP2PRequest has a [[StartPromise]], a Promise or null, initially null; an [[Abort]], steps that take a reason, or null, initially null; and a [[Nickname]], a string.
LP2PRequest(options) constructor steps are:
-
Set this.
[[Nickname]]to options["nickname"] if it exists, or the empty string otherwise.
start(options) method steps are to return the result of starting this given options["signal"] if it exists, or null otherwise.
AbortSignal or null signal:
-
If signal is not null and is aborted, return a promise rejected with signal’s abort reason.
-
If request.
[[StartPromise]]is not null, return it. -
Let global be request’s relevant global object.
-
Let origin be request’s relevant settings object’s origin.
-
If global is not permitted to use the "local-peer-to-peer" policy-controlled feature, as described in § 3 Permission Policy Integration, or origin is an opaque origin, return a promise rejected with a "
NotAllowedError"DOMException. -
Let window be the relevant global object of the user interface document of global, or null if there is none. If window is null or does not have transient activation, return a promise rejected with a "
NotAllowedError"DOMException. Otherwise, consume user activation of window. -
Let promise be a new promise.
-
Set request.
[[StartPromise]]to promise. -
Let aborted be false.
-
Let fail be these steps: queue a global task on the networking task source given global to run these steps: if aborted is false, set request.
[[StartPromise]]to null and reject promise with a "NotAllowedError"DOMException. -
Set request.
[[Abort]]to these steps given a reason:-
If promise is settled, abort these steps.
-
Set aborted to true.
-
Set request.
[[StartPromise]]to null. -
Reject promise with reason.
-
-
If signal is not null, add the following abort steps to signal: run request.
[[Abort]]given signal’s abort reason. -
Run these steps in parallel. Whenever aborted becomes true, take down any user interface they show, stop discovering peers, close session if it was established, and abort these steps:
-
Let advertisement be the result of acquire a local peer grant for origin, presenting request.
[[Nickname]]to the user. -
If advertisement is null, run fail and abort these steps.
-
Let peer be the peer of advertisement.
-
Let session be the result of establish a session with advertisement given origin. If this fails, run fail and abort these steps.
-
Wait until session is authenticated and the server has accepted it. Meanwhile, run authenticate a session on session when the transport binding calls for it. This may be a trust ceremony. If authentication fails, session is authenticated with a peer identity other than the one peer claimed, the server refuses session, or session is closed, close session, run fail and abort these steps.
-
If session was closed, run fail and abort these steps.
-
Add a known peer with peer, and grant a peer given origin, peer’s peer identity and "
selected". -
Queue a global task on the networking task source given global to run these steps:
-
If aborted is true, close session and abort these steps.
-
If session was closed, set request.
[[StartPromise]]to null, reject promise with a "NotAllowedError"DOMException, and abort these steps. -
Let connection be the result of creating an LP2PConnection given session and global.
-
Resolve promise with connection.
-
-
Return promise.
Calling start() again while the request is pending, or after it succeeded, returns the same promise, and ignores the new options. Aborting after it succeeded does nothing; close the connection instead. After a rejection, start() can be called again to start over.
Every failure rejects with the same error, so that script cannot tell whether the user dismissed the picker, or picked a peer that then could not be reached, could not be authenticated or refused the session. Refusal after the user picked a peer is expected: discovery does not carry the requesting origin, so the picker can list peers whose receivers do not allow it.
8.1. LP2PRequestOptions
dictionary {LP2PRequestOptions DOMString ; };nickname dictionary {LP2PRequestStartOptions AbortSignal signal ; };
signal, of type AbortSignal-
Aborts
start(), assignalabortsfetch(): the promise rejects with the signal’s abort reason, and the picker or the session being set up is taken down.
8.2. Examples
Example: Setting up a request for a connection:
const request= new LP2PRequest({ nickname: "example-request" , }); try { // Blocks until the user picked a peer and it accepted the session. const conn= await request. start(); console. log( "Connection established!" ); // Later: close the connection, its data channels and its transports. conn. close(); } catch ( e) { // NotAllowedError: dismissed, refused or failed; script can't tell which. }
9. The LP2PConnection Interface
The LP2PConnection interface represents a connection with another local peer.
[Exposed =(Window ,DedicatedWorker ,SharedWorker ),SecureContext ]interface LP2PConnection :EventTarget {undefined close ();attribute EventHandler onclose ; };
An LP2PConnection has a [[Session]], a Local Peer-to-Peer session; a [[Transports]], a set of LP2PQuicTransport objects, initially empty; and a [[Closed]], a boolean, initially false.
-
Let connection be a new
LP2PConnectionin global’s relevant realm. -
Set connection.
[[Session]]to session. -
Return connection.
-
If connection.
[[Closed]]is true, return. -
Close connection.
[[Session]]. -
Queue a global task on the networking task source given connection’s relevant global object to run finalize given connection.
When the transport binding reports that the [[Session]] of an LP2PConnection connection was closed, by the peer or because the underlying transport failed, the user agent must queue a global task on the networking task source given connection’s relevant global object to run finalize given connection.
LP2PConnection connection:
-
If connection.
[[Closed]]is true, return. -
Set connection.
[[Closed]]to true. -
For each transport of connection.
[[Transports]], fail transport abruptly with aWebTransportErrorwhosesourceis "session", asWebTransportdoes when its session is terminated without a close code, unless it is already closed or failed. -
If connection.
[[Listener]]is not null, close it. -
Fire an event named
closeat connection.
Decide whether LP2PConnection also gets a keying material exporter, for pages that only use data channels. It would need a per-connection context, since several connections with the same peer can share the underlying transport.
The close event fires at most once. Closing the session also closes all of its data channels.
9.1. Events
| Event name | Interface | Fired when… |
|---|---|---|
close
| Event
| The connection is closed, by close(), by the peer, or because the transport failed.
|
The onclose event handler IDL attribute has the event handler event type close.
10. The LP2PDataChannel Interface
This section is not yet aligned with the rest of this specification, nor fully specified.
The LP2PDataChannel interface represents a bi-directional data channel between two peers. An LP2PDataChannel is created via a factory method on a LP2PConnection object. It is backed by a data channel on the connection’s Local Peer-to-Peer session.
Note: The LP2PDataChannel interface is purposefully kept as close as possible to the RTCDataChannel interface defined in [webrtc]. The aim is to allow seamless transition of developers that are familiar with WebRTC as well as allowing libraries to easily work with both the [webrtc] and LP2P API.
[Exposed =(Window ,DedicatedWorker ,SharedWorker ),SecureContext ]interface :LP2PDataChannel EventTarget {(constructor USVString ,label optional LP2PDataChannelInit = {});dataChannelDict readonly attribute USVString ;label readonly attribute USVString ;protocol readonly attribute unsigned short ?;id attribute EventHandler onopen ;attribute EventHandler onerror ;attribute EventHandler onclosing ;attribute EventHandler onclose ;undefined ();close attribute EventHandler onmessage ;attribute BinaryType ;binaryType undefined ((send USVString or Blob or ArrayBuffer or ArrayBufferView )); };data
10.1. LP2PDataChannelInit
dictionary {LP2PDataChannelInit USVString = ""; [protocol EnforceRange ]unsigned short ; };id
10.2. Events
The following event is fired at the LP2PDataChannel object:
| Event name | Interface | Fired when… |
|---|---|---|
open
| Event
| The data channel is opened. |
message
| MessageEvent[html]
| An incoming message is received. |
error
| Event
| An error occurred. |
closing
| Event
| The data channel is closing. |
close
| Event
| The data channel is closed. |
10.3. Event handlers
The following are the event handlers (and their corresponding event handler event types) that must be supported, as event handler IDL attributes, by all objects implementing the LP2PDataChannel interface:
| event handler | event handler event type |
|---|---|
onopen
| open
|
onmessage
| message
|
onerror
| error
|
onclosing
| closing
|
onclose
| close
|
10.4. LP2PConnection Interface Extensions
[Exposed =(Window ,DedicatedWorker ,SharedWorker ),SecureContext ]partial interface LP2PConnection {LP2PDataChannel (createDataChannel USVString ,label optional LP2PDataChannelInit = {});dataChannelDict attribute EventHandler ondatachannel ; };
createDataChannel() throws a "NotSupportedError" DOMException if the capabilities of the connection’s peer do not include data channels.
10.5. LP2PDataChannelEvent
In general, when defining a new interface that inherits from Event please always ask feedback from the WHATWG or the W3C WebApps WG community. See defining event interfaces.
[Exposed =(Window ,DedicatedWorker ,SharedWorker ),SecureContext ]interface :LP2PDataChannelEvent Event {(constructor DOMString ,type LP2PDataChannelEventInit );dataChannelEventInitDict readonly attribute LP2PDataChannel ; };channel dictionary :LP2PDataChannelEventInit EventInit {required LP2PDataChannel ; };channel
10.6. Extensions Events
The following event is fired at the LP2PConnection object:
| Event name | Interface | Fired when… |
|---|---|---|
datachannel
| LP2PDataChannelEvent
| An incoming data channel is received. |
An incoming data channel that is reported before its LP2PConnection is exposed to script, that is, before start() resolves or the connection event is dispatched, is held until then, so that script can set ondatachannel first. This is the same rule as for incoming WebTransport sessions.
10.7. Extensions Event handlers
The following are the event handlers (and their corresponding event handler event types) that must be supported, as event handler IDL attributes, by all objects implementing the LP2PConnection interface:
| event handler | event handler event type |
|---|---|
ondatachannel
| datachannel
|
10.8. Examples
Example: Receive a LP2PDataChannel on an existing connection as receiver:
const receiver= new LP2PReceiver({ nickname: "example-receiver" , }); receiver. onconnection= e=> { const conn= e. connection; console. log( "Receiver: Got a connection!" ); conn. ondatachannel= e=> { const channel= e. channel; channel. onmessage= e=> { const message= e. data; console. log( `Receiver: Received message: ${ message} ` ); }; channel. send( "Good day to you, requester!" ); }; }; await receiver. start();
Example: Create a LP2PDataChannel on an existing connection as requester:
const request= new LP2PRequest({ nickname: "example-request" , }); const conn= await request. start(); console. log( "Requester: Got a connection!" ); const channel= conn. createDataChannel( "My Channel" ); channel. onopen= e=> { channel. onmessage= e=> { const message= e. data; console. log( `Requester: Received message: ${ message} ` ); }; channel. send( "Good day to you, receiver!" ); };
11. The LP2PQuicTransport Interface
The LP2PQuicTransport interface is a [webtransport] WebTransport over an LP2PConnection. It is backed by a WebTransport session: a session obtained with open a WebTransport session on the connection’s [[Session]], or an incoming WebTransport session on it.
[Exposed =(Window ,DedicatedWorker ,SharedWorker ),SecureContext ]interface LP2PQuicTransport :WebTransport {constructor (LP2PConnection ,connection optional LP2PQuicTransportInit = {});options readonly attribute LP2PConnection connection ; };dictionary {LP2PQuicTransportInit boolean requireUnreliable =false ;WebTransportCongestionControl = "default"; [congestionControl EnforceRange ]unsigned short ?=anticipatedConcurrentIncomingUnidirectionalStreams null ; [EnforceRange ]unsigned short ?=anticipatedConcurrentIncomingBidirectionalStreams null ; };
The members of LP2PQuicTransportInit have the meaning of the WebTransportOptions members of the same name. WebTransportOptions members that concern HTTP or a server, such as allowPooling, serverCertificateHashes and protocols, are left out.
requireUnreliable, of type boolean, defaulting tofalse-
If true, the transport fails unless its WebTransport session delivers datagrams unreliably, as for an HTTP/2 fallback in [webtransport]. A transport binding that cannot lose messages provides only "
reliable-only" sessions; see open a WebTransport session.
LP2PQuicTransport inherits the attributes and methods of WebTransport, but not its constructor. [webtransport] defines the internal slots and algorithms of WebTransport in terms of a WebTransport session over HTTP/3 or HTTP/2. For an LP2PQuicTransport they apply to its WebTransport session instead: its streams, datagrams, close and draining are those of the transport binding.
WebTransport constructor does, given the WebTransportOptions members of options, except that there is no URL, no HTTP headers, no server certificate hashes and no pooling, and its protocol is the empty string.
Its exportKeyingMaterial() method runs export keying material on its WebTransport session, in place of the exporter of WebTransport over HTTP/3. Each WebTransport session has its own exporter.
An LP2PQuicTransport also has a [[Connection]], an LP2PConnection. The connection getter steps are to return this.[[Connection]].
Ask the WebTransport editors for a hook to create a WebTransport over a session that is not HTTP, instead of initializing its internal slots here.
LP2PQuicTransport(connection, options) constructor steps are to set up an LP2PQuicTransport this given connection and options.
LP2PConnection connection and options:
-
If connection.
[[Closed]]is true, throw an "InvalidStateError"DOMException. -
If the capabilities of the peer of connection.
[[Session]]do not include WebTransport, throw a "NotSupportedError"DOMException. They are known when the connection exists, so this fails synchronously, asWebTransport’s constructor does for options it cannot support. -
Initialize an LP2PQuicTransport transport given options. Its state is "
connecting" and itsreliabilityis "pending". -
Set transport.
[[Connection]]to connection and append transport to connection.[[Transports]]. -
Run these steps in parallel:
-
Let session be the result of open a WebTransport session on connection.
[[Session]]. -
Queue a global task on the networking task source given transport’s relevant global object to run these steps:
-
If opening failed, or the peer refused, fail transport with a
WebTransportErrorwhosesourceis "session", asWebTransportdoes when establishing a session fails, and abort these steps. -
If transport’s state is not "
connecting", close session and abort these steps. -
If options["
requireUnreliable"] is true and session is "reliable-only", close session, fail transport as above, and abort these steps. -
Associate transport with session, set its
reliabilityto that of session, set its state to "connected" and resolve itsreadypromise.
-
-
Each construction opens a new WebTransport session. If both peers construct an LP2PQuicTransport on the same connection, there are two sessions. To receive the peer’s sessions, use an LP2PQuicTransportListener.
11.1. Lifecycle
An LP2PQuicTransport and its LP2PConnection affect each other as follows:
-
close()on the transport runs close a WebTransport session with itscloseCodeandreason. The peer’s transport is closed cleanly with them. TheLP2PConnectionstays open. -
If the WebTransport session is closed abruptly or fails, the transport fails with a
WebTransportErrorwhosesourceis "session". TheLP2PConnectionstays open. -
When the WebTransport session is drained by the peer with drain a WebTransport session, the transport’s
drainingpromise resolves. -
When the
LP2PConnectioncloses, byclose(), by the peer or because the transport failed, each of its transports fails with aWebTransportErrorwhosesourceis "session", and its listener is closed, before thecloseevent fires. See finalize. -
stop()affects neither connections already delivered nor their transports and listeners.
11.2. LP2PQuicTransportListener Interface
The LP2PQuicTransportListener interface receives the incoming WebTransport sessions of an LP2PConnection.
[Exposed =(Window ,DedicatedWorker ,SharedWorker ),SecureContext ]interface LP2PQuicTransportListener {constructor (LP2PConnection ,connection optional LP2PQuicTransportListenerInit = {});options readonly attribute ReadableStream incomingTransports ;undefined close (); };dictionary :LP2PQuicTransportListenerInit LP2PQuicTransportInit { };
The options of an LP2PQuicTransportListener apply to each LP2PQuicTransport it creates.
An LP2PQuicTransportListener has a [[Connection]], an LP2PConnection; an [[Options]], an LP2PQuicTransportListenerInit; an [[IncomingTransports]], a ReadableStream; an [[OwnsStream]], a boolean; and a [[Closed]], a boolean, initially false. An LP2PConnection has a [[Listener]], an LP2PQuicTransportListener or null, initially null.
LP2PQuicTransportListener(connection, options) constructor steps are to set up an LP2PQuicTransportListener this given connection and options.
LP2PConnection connection, options and an optional ReadableStream shared:
-
If connection.
[[Closed]]is true, or connection.[[Listener]]is not null, throw an "InvalidStateError"DOMException. -
Set listener.
[[Connection]]to connection and listener.[[Options]]to options. -
If shared is given, set listener.
[[IncomingTransports]]to shared and listener.[[OwnsStream]]to false. The listener then delivers into shared, and its owner closes it. -
Otherwise, let stream be a new
ReadableStream. Set up stream with cancelAlgorithm set to an algorithm that closes listener, and highWaterMark set to an implementation-defined positive number. Set listener.[[IncomingTransports]]to stream and listener.[[OwnsStream]]to true. -
Set connection.
[[Listener]]to listener.
There is at most one listener per LP2PConnection at a time.
The incomingTransports getter steps are to return this.[[IncomingTransports]]. Its chunks are LP2PQuicTransport objects.
-
If listener.
[[Closed]]is true, return. -
Set listener.
[[Closed]]to true. -
Set listener.
[[Connection]].[[Listener]]to null. -
If listener.
[[OwnsStream]]is true, close listener.[[IncomingTransports]], unless it is already closed or errored. Transports already queued can still be read.
After close(), a new listener can be constructed on the same connection.
[[Session]] of an LP2PConnection connection, the user agent must queue a global task on the networking task source given connection’s relevant global object to run these steps:
-
Let listener be connection.
[[Listener]]. -
If listener is null, or the desired size to fill up to the high water mark of listener.
[[IncomingTransports]]is not a positive number, refuse session and abort these steps. -
If listener.
[[Options]]["requireUnreliable"] is true and session is "reliable-only", refuse session and abort these steps. -
Let transport be a new
LP2PQuicTransportin connection’s relevant realm. -
Initialize an LP2PQuicTransport transport given listener.
[[Options]], set transport.[[Connection]]to connection and append transport to connection.[[Transports]]. -
Accept session, associate transport with it, set its
reliabilityto that of session, set its state to "connected" and resolve itsreadypromise. -
Enqueue transport into listener.
[[IncomingTransports]].
So an incoming WebTransport session is refused when there is no listener, or when the listener’s queue is full because script is not reading it. An incoming WebTransport session that is reported before its LP2PConnection is exposed to script, that is, before start() resolves or the connection event is dispatched, is held until then, so that script can construct a listener first.
11.3. Examples
Example: The requester opens a LP2PQuicTransport and the receiver listens for it:
// Receiver const receiver= new LP2PReceiver({ nickname: "example-receiver" , }); receiver. onconnection= async e=> { const listener= new LP2PQuicTransportListener( e. connection); for await ( const transportof listener. incomingTransports) { // Incoming transports are already ready. const stream= await transport. createBidirectionalStream(); } }; await receiver. start(); // Requester const request= new LP2PRequest({ nickname: "example-request" , }); const conn= await request. start(); const transport= new LP2PQuicTransport( conn); await transport. ready;
Either side can open: the receiver can construct an LP2PQuicTransport on its connection while the requester listens.
Refer to the WebTransport examples for usage of a [webtransport] object.
12. Shorthand APIs
The lp2p namespace provides shorthands for the common case of one side making itself discoverable and the other connecting, with WebTransport. They are composed from the APIs above, as if script used them, and add no capability. They cover LP2PQuicTransport only: a connection over which the peer only opens data channels never shows up in makeDiscoverable(). On both sides, connection gives access to the LP2PConnection, for example to open data channels or close it.
[Exposed =(Window ,DedicatedWorker ,SharedWorker ),SecureContext ]namespace lp2p {ReadableStream makeDiscoverable (optional LP2PMakeDiscoverableOptions = {});options Promise <LP2PQuicTransport >connect (optional LP2PConnectOptions = {}); };options dictionary :LP2PMakeDiscoverableOptions LP2PQuicTransportListenerInit {DOMString ;nickname sequence <USVString >= []; };allowedOrigins dictionary :LP2PConnectOptions LP2PQuicTransportInit {DOMString ;nickname AbortSignal ; };signal
The members of LP2PMakeDiscoverableOptions and LP2PConnectOptions have the meaning of the members of the same name of LP2PReceiverOptions, LP2PRequestOptions and LP2PRequestStartOptions.
makeDiscoverable() is roughly equivalent to the following JavaScript, except that it uses no public methods or attributes, so script can’t intercept it, and that its listeners share one stream. Where they differ, the algorithm wins.
lp2p. makeDiscoverable= options=> { const receiver= new LP2PReceiver( options), listeners= new Set(); let controller; const stream= new ReadableStream({ start: c=> { controller= c; }, cancel: () => { receiver. stop(); listeners. forEach( l=> l. close()); } }); receiver. onconnection= async ({ connection}) => { const listener= new LP2PQuicTransportListener( connection, options); listeners. add( listener); for await ( const transportof listener. incomingTransports) controller. enqueue( transport); }; receiver. start(). catch ( e=> controller. error( e)); return stream; };
makeDiscoverable(options) method steps are:
-
Let realm be the current global object’s relevant realm.
-
Let listeners be an empty set.
-
Let receiver be a new
LP2PReceiverin realm. -
Let stream be a new
ReadableStreamin realm. Set up stream with cancelAlgorithm set to an algorithm that stops receiver and closes each item of listeners, and highWaterMark set to an implementation-defined positive number. -
Set up an LP2PReceiver receiver given an
LP2PReceiverOptionswith thenicknameandallowedOriginsmembers of options, and these steps given anLP2PConnectionconnection: let listener be a newLP2PQuicTransportListenerin realm, set up an LP2PQuicTransportListener listener given connection, theLP2PQuicTransportListenerInitmembers of options and stream, and append listener to listeners. Rethrow any exception. -
Upon rejection of the result of starting receiver with reason, error stream with reason.
-
Return stream.
The returned stream’s chunks are LP2PQuicTransport objects, already ready. Each connection’s listener enqueues into it, so when script doesn’t read it and it is full, further sessions are refused, as described in § 11.2 LP2PQuicTransportListener Interface. makeDiscoverable() owns the listener of each connection it creates, so constructing another LP2PQuicTransportListener on it throws. Cancelling the stream stops advertising and closes the listeners; as with stop(), connections already made stay open.
connect() is roughly equivalent to the following JavaScript, except that it uses no public methods or attributes. Where they differ, the algorithm wins.
lp2p. connect= async options=> { const connection= await new LP2PRequest( options). start( options); const transport= new LP2PQuicTransport( connection, options); try { await transport. ready; } catch ( e) { connection. close(); throw e; } return transport; };
connect(options) method steps are:
-
Let request be a new
LP2PRequestin the current global object’s relevant realm, with its[[Nickname]]set to options["nickname"] if it exists, or the empty string otherwise. -
Let signal be options["
signal"] if it exists, or null otherwise. -
Let promise be a new promise.
-
Let started be the result of starting request given signal.
-
Upon rejection of started with reason, reject promise with reason.
-
Upon fulfillment of started with connection:
-
If signal is not null and is aborted, close connection, reject promise with signal’s abort reason, and abort these steps.
-
Let transport be a new
LP2PQuicTransportin the current global object’s relevant realm. Set up an LP2PQuicTransport transport given connection and theLP2PQuicTransportInitmembers of options. If this throws an exception error, close connection, reject promise with error, and abort these steps. -
Upon fulfillment of transport’s [[Ready]] promise, resolve promise with transport.
-
Upon rejection of it with reason, close connection and reject promise with reason.
-
-
Return promise.
connect() resolves once the user picked a peer and the WebTransport session is up. It creates no listener, so script can construct one on connection. Aborting signal takes down the picker or the session being set up, as for start().
12.1. Examples
// Peer A for await ( const transportof lp2p. makeDiscoverable({ nickname: "Peer A" })) { const stream= await transport. createBidirectionalStream(); } // Peer B const transport= await lp2p. connect({ nickname: "Peer B" }); const channel= transport. connection. createDataChannel( "chat" );
13. Document and worker lifecycle
When a Window’s associated Document stops being fully active, for example because it is unloaded or its frame is removed, or when a WorkerGlobalScope’s closing flag is set, the user agent must run these steps given that global object global:
-
For each
LP2PReceiverwhose relevant global object is global, stop it. -
For each
LP2PRequestwhose relevant global object is global and whose[[StartPromise]]is pending, run its[[Abort]]given an "AbortError"DOMException. This takes down its user interface and closes the session being set up. -
For each
LP2PConnectionwhose relevant global object is global, close it.
Decide whether a document with receivers or connections may enter the back/forward cache instead, and what happens to them there.
14. Security and privacy considerations
Three guarantees are independent of each other: peer authentication, that a peer is the device the user paired with or confirmed (§ 4.5 Peer authentication); origin isolation, that in each browser a session is exposed only to the origin the user granted it to (§ 4.6 Peer grant, § 4.3 Peer advertisement); and application authorization, which is for the application to decide. None implies another. Authenticating a peer, or accepting its asserted requesting origin, must never implicitly grant any other capability, such as access to files, the network, storage or permissions.
Note: An application that needs proof of the remote origin can bind credentials its own origin issued to the session, with exportKeyingMaterial() on an LP2PQuicTransport, see export keying material. Once provisioned, this works offline. It proves that the origin issued the credential, not what runtime holds it on the other side.
14.1. Hardening before authentication
Before authentication, a user agent handles traffic from any device on the local communication medium. Implementations parse it as untrusted input and bound what it can cost: limits on connections and handshakes, timeouts, resource limits against denial of service, and isolation of the implementation from the rest of the user agent.
Treat hardening before authentication in more detail.
14.2. Transport bindings
The security and privacy considerations of the protocol underlying the transport binding in use must be considered when implementing this API. For § 15 Binding to the Open Screen Network Protocol, these are the Security and Privacy considerations of the Open Screen Network Protocol.14.3. Personally identifiable information
No peer attributes reach origins: the peer identity, display name, capabilities and the nickname and advertisement ID of its advertisements are only used by the user agent. Its user interface shows the display name and nickname to the user, to pick or accept a peer.No information is exposed to the origin during service discovery. No IP information is exposed to an origin. This is fully managed by the user agent.
Refine the peer attributes exposed to origins. [WICG/local-peer-to-peer Issue #15]
14.4. User interface guidelines
When the user is asked to grant permission to connect to a peer, the user agent should make it clear what origin the request is coming from.
Define peer filtering to give the user additional context. [WICG/local-peer-to-peer Issue #15]
14.5. Authentication and locality
Peers authenticate each other out of band, usually by a trust ceremony in which the user takes part on one or both devices. Other means, such as a key provisioned in advance, could be defined later.
This specification does not require or rely on physical proximity. "Local" describes the envisioned use cases and the local communication medium, not a security guarantee: a peer can be further away than the user expects, for example on a large or misconfigured network or behind an intermediary, and a transport binding need not verify where it is.
So a peer, including a headless device, must not authorize anything because a request arrives over the local communication medium, or because of the requesting origin it asserts. It authorizes only peers it authenticated, and an application only with credentials that its origin issued, bound to the session with export keying material. This differs from protocols such as UPnP, where being on the network is the authorization.
14.6. Protecting users
The user is asked only what they can judge: which device to use, and which site may use it. The user agent does not hand the user security decisions it can make itself. In particular:
-
start()andstart()require transient activation, so that a page cannot prompt without a user action. -
Trusted user interface clearly distinguishes strings chosen by script or asserted by a peer, such as an nickname or an unverified display name, from those the user agent verified, such as an origin or a display name obtained on an authenticated session. It bounds their length and never presents them as instructions.
-
User agents limit repeated prompts, for example by rate-limiting grant prompts per peer and per origin, remembering a decline for a while, and timing out incoming sessions that wait for a prompt.
-
The user agent shows an indicator while it advertises and while a session is open. The prompt to advertise says that every device on the local communication medium can see the nickname.
Note: These are principles. How the user interface meets them is not specified further.
14.7. Impact on same-origin policy
This document extends the Web platform with the ability to set up real-time, direct communication between browsers and other devices, including other browsers, within a local communication medium.
This means that data and media can be shared between applications running in different browsers, or between an application running in the browser and another user agent that is not a browser such as a headless service provided by a smart TV or smart fridge. This extends the usual barriers in the Web’s security model that prevents sending data between entities with different origins.
To limit this, a session is only delivered to an LP2PReceiver of the same origin as its requesting origin, unless the receiver opts in to other origins, or with "*" to any requester, with allowedOrigins. The requesting origin is asserted by the peer’s user agent, so a peer can claim any origin. Such a peer is authenticated: the user paired with it in a trust ceremony, or a vouching party they trust vouches for it and they confirmed it, although they may never have paired with it. Authentication identifies the device, not its intent or the origin of the page using it.
14.8. Device Access
The Local Peer-to-Peer API requires user permission for a page to access any peers. The API uses purpose-fit protocols for data communication. It cannot be used to connect to raw sockets or unknowing HTTP servers. As in start(), the user first picks a peer in acquire a local peer grant; the session with it is then authenticated, with a trust ceremony if needed, and accepted by the peer; only then is the grant recorded with grant a peer.
14.9. Persistent State
The persistent state rules of the transport binding in use must be followed when implementing this API. For § 15 Binding to the Open Screen Network Protocol, these are the Open Screen Network Protocol Persistent State rules.14.10. Secure Contexts
The Local Peer-to-Peer API must only be provided in a secure context.15. Binding to the Open Screen Network Protocol
This section defines a transport binding to the Open Screen Network Protocol (OSP). In this binding, a peer is an OSP agent and a Local Peer-to-Peer session is an authenticated QUIC connection between two agents. Each Local Peer-to-Peer session has its own QUIC connection, even with the same agent, so that sessions are not shared between origins.
To discover metadata of an agent, the user agent follows Open Screen Network Protocol transport and metadata discovery.
The concepts and operations of § 6 Transport abstraction map onto OSP as follows:
Define how a data channel is closed.
Part of these operations are defined below as protocol extensions to the Open Screen Network Protocol.
15.1. Advertisement extension
An agent advertises one DNS-SD service instance, however many advertisements it has. Start advertising adds an advertisement with an advertisement ID of 16 bytes from a cryptographically secure random number generator, and starts advertising the service instance if it is the first. Stop advertising removes the advertisement, and stops advertising the service instance after the last one. Whenever its set of advertisements changes, the agent increases its metadata version (mv).
During discover metadata, after the agent-info-response, the listening agent sends an lp2p-advertisements-request message on a new bidirectional QUIC stream. The advertising agent answers on the same stream with an lp2p-advertisements-response message that lists, for each of its advertisements, its advertisement ID and nickname. Discover peers reports each of them as an advertisement of that agent.
Like agent-info, the listing is sent before authentication, to any agent that can reach the advertising agent. It reveals the number of advertisements, their nicknames and their advertisement IDs; it does not reveal origins. The advertisement IDs do not link one advertisement to another, but the agent fingerprint links them all.
The agent fingerprint in the mDNS TXT record, and the agent certificate that OSP presents on every QUIC connection, including metadata connections, identify the agent before authentication, across advertisements and networks. The nicknames in the listing are also sent before authentication. This binding does not fix either.
15.2. Session extension
To establish a session with an agent given an origin, the client opens a new QUIC connection to the agent, as described in transport. Both agents send auth-capabilities when the connection starts, as in OSP, so that both know which agent presents the PSK; this message does not start authentication. Only the client starts authentication, although OSP otherwise has an agent initiate authentication with an agent it has not verified.
When the client is the PSK consumer, it starts authentication with psk-needs-presentation. As written, OSP requires that message to carry pA, which needs the PSK the presenter has not created yet. This binding assumes OSP is fixed so that the message carries no public-value, and the presenter then starts the presenter-initiated flow.
This binding conveys the requesting origin only after authentication:
-
Once it has received the agent’s agent-info-response, by discover metadata on that connection, the client starts authentication if it has not verified the server’s agent certificate.
-
Once it has verified it, the client sends an lp2p-session-request message on a new bidirectional QUIC stream.
-
A server that has not verified the client’s agent certificate answers with a provisional lp2p-session-response with the result
authentication-required, without looking at the requesting-origin or the advertisement-id, and the client then starts authentication. The server sends no authentication message other than auth-capabilities before the client does. -
Once it has verified the client’s agent certificate, the server reports the connection as an incoming session for the advertisement whose advertisement ID is the advertisement-id, with the requesting-origin as its requesting origin. If it has no such advertisement, it refuses the session.
So the requesting-origin and advertisement-id are only sent to a verified server, and the server’s answer about it only reaches a verified client. Agents that have verified each other run no trust ceremony.
The lp2p-session-request contains:
- requesting-origin
-
The ASCII serialization of the origin, or absent if the requesting origin is null. The server uses null if it is absent, and otherwise the origin of the result of running the URL parser on this value, or an opaque origin if parsing fails.
- advertisement-id
-
The advertisement ID of the advertisement the client establishes the session with.
A client sends lp2p-session-request only to an agent whose capabilities are not empty, and only once per QUIC connection. A server that receives a second one closes the connection.
Authentication verifies the agent certificate that each agent presented in the TLS handshake of the QUIC connection that carries the lp2p-session-request: SPAKE2 uses the fingerprints of those certificates, and a certificate verified earlier is bound by the handshake itself. So the requesting-origin and advertisement-id are bound to the Local Peer-to-Peer session.
The server answers on the same stream with an lp2p-session-response message, containing:
- result
-
authentication-required, provisional, as above; a final result follows on the same stream.successif the server accepts the session, orpermanent-errorif it refuses it. The server sends either only once it has verified the client’s agent certificate. After a refusal, the client ends its side of the stream, and the server closes the QUIC connection once it has, or after an implementation-defined timeout, so that the response is not lost.
The client treats the session as accepted once it has received success and has also verified the server’s agent certificate. Both agents then end their side of the stream.
The server does not send messages of the other extensions in this section on the connection before it has sent success, and the client not before it treats the session as accepted. The server may open data channels and WebTransport sessions right after sending success; the client holds streams that arrive before it treats the session as accepted until it does, and discards them if the session is not. To close a session, an agent closes the QUIC connection.
15.3. Data channel extension
In order to signal support for this protocol extension, the agent should include the data-channels agent-capability as part of the agent-info-response message exchanged during discover metadata. An agent does not send data-channel-open-request to an agent whose capabilities lack data channels.
To open a data channel, an agent may send a data-channel-open-request message on a new QUIC stream. The Local Peer-to-Peer session must be authenticated. The message must contain the following values:
- channel-id
-
An ID number between 0 and 65,534 which uniquely identifies the data channel. When the binding chooses the id, the client uses an even number and the server an odd number. The value 65,535 is reserved, as for
RTCDataChannel;idis anunsigned shortfor parity withid, but cannot take that value. - label
-
a string that contains a name describing the data channel. These labels are not required to be unique.
- protocol
-
a string containing the name of the subprotocol in use. If no protocol was specified when the data channel was created, then this property’s value is the empty string ("").
The receiving agent reports the data-channel-open-request as an incoming data channel. When the receiver receives the data-channel-open-request, it should send back a data-channel-open-response message. The response must include the following:
- result
-
a code indicating success or failure, and the reason for the failure. If the user agent refuses the incoming data channel, the result indicates failure.
If the data-channel-open-response message indicates success, the data channel is considered open. Agents can now send data on a channel by sending data-frame messages (type key 24) on the same QUIC stream the data channel was opened. The message must include the following keys; the other keys of data-frame (1, 2, 3 and 5) are unused, should not be sent, and must be ignored by the receiving agent:
- encoding-id
-
Determines the encoding of the data being sent. The values are specified as data-channel-encoding-id, instead of referencing a data encoding offer: 0: Blob; 1: String; 2: ArrayBuffer.
- payload
-
The binary representation of the data being sent.
A text message is sent with String; a binary message with Blob or ArrayBuffer. The receiving agent reports each data-frame as a received message, which is text if its encoding-id is String and binary otherwise.
15.4. WebTransport extension
The protocol extension to send data on a channel provides an ergonomic way to send simple messages. An agent can open a WebTransport session for communication that requires low overhead and more granular streams control.
An agent that supports this extension includes the quic-transport agent-capability in its agent-info-response, obtained by discover metadata, and must support QUIC DATAGRAM frames [RFC9221] on each connection that carries WebTransport sessions, so that its sessions are "supports-unreliable". An agent must not send the messages of this extension to an agent whose capabilities lack WebTransport, since an unknown type key closes the whole connection; open a WebTransport session fails instead. The capability describes the agent, not an origin: whether a page takes a session is answered by data-transport-start-response.
A WebTransport session is carried either on the QUIC connection of its Local Peer-to-Peer session, next to the other streams of that connection, or on a dedicated connection. Either agent can open a session on the QUIC connection of the Local Peer-to-Peer session; only the client opens a dedicated connection. So both agents can open a WebTransport session, as § 6.1 Peers and sessions requires.
15.4.1. Sessions
To open a WebTransport session, an agent opens a bidirectional QUIC stream, the session’s control stream, and sends a data-transport-start-request message on it, containing:
- exchange-id
-
A number below 262 that identifies the session within its Local Peer-to-Peer session, across that session’s QUIC connection and dedicated connections. The client uses even numbers and the server odd numbers, and neither reuses one.
- session-binding
-
Only on a dedicated connection, see § 15.4.5 Dedicated connections.
The other agent reports an incoming WebTransport session. On the same stream it answers with a data-transport-start-response message, whose result is success if the session is accepted. Otherwise, it sends permanent-error and ends the stream with FIN; the session never opens. The opening agent does not open streams or send datagrams for the session before it receives success, and abandons a session it has had no response for by resetting the control stream.
15.4.2. Streams and datagrams
Each WebTransport stream is a QUIC stream with the same directionality, opened on the connection that carries the session. Its first message, sent by the agent that opened it, is a data-transport-stream message with the session’s exchange-id. The WebTransport stream’s data follows right after it, the other direction of a bidirectional stream carries only data, and FIN ends the stream. No response is sent.
A WebTransport datagram is a QUIC DATAGRAM frame whose payload is the session’s exchange-id, encoded as a QUIC variable-length integer [RFC9000], followed by the datagram. Datagrams for a session that is not open are dropped.
A WebTransport stream is reset or stopped with RESET_STREAM or STOP_SENDING. The QUIC application error code is the WebTransport stream error code, from 0 to 232−1. Codes from 232 are reserved for this binding:
- session-gone (232)
-
The stream’s session is closed or unknown. It is reported to script without a stream error code.
An unknown code from 232 is treated as session-gone. An agent may hold, for a short time, a stream for a session that is not yet open, such as one the accepting agent opened right after sending success. Otherwise it refuses the stream with STOP_SENDING, and for a bidirectional stream also RESET_STREAM, with session-gone. A stream reset before its first message arrived is discarded.
15.4.3. Close and drain
To close a WebTransport session, an agent sends a data-transport-close message on the control stream and then ends the stream with FIN. It contains:
- close-code
-
The close code, from 0 to 232−1.
- reason
-
The reason, UTF-8 encoded, at most 1,024 bytes.
An agent that receives data-transport-close or a FIN on the control stream ends its side of it too. Both agents then reset and stop all streams of the session with session-gone. A control stream that ends with FIN without a data-transport-close closes the session with code 0 and an empty reason. A control stream that is reset, or a connection that closes, closes the session abruptly. So an agent that sent data-transport-close closes the QUIC connection only once the peer has ended its side of the control stream, or after an implementation-defined timeout, so that the message is not lost.
To drain a WebTransport session, an agent sends a data-transport-drain message on the control stream. An agent may do so when it intends to close the session soon.
15.4.4. Keying material exporters
To export keying material from a WebTransport session, an agent uses the TLS exporter of the QUIC connection that carries the session, with the label "EXPORTER-WebTransport" and, as context, the WebTransport Exporter Context of WebTransport over HTTP/3, Section 4.8, with the exchange-id as its session ID.
15.4.5. Dedicated connections
The client may carry a WebTransport session on a dedicated connection instead, for example to give it its own congestion control. It opens a new QUIC connection to the agent with the ALPN token "q2q" and the same agent certificate as its Local Peer-to-Peer session. The client closes the connection unless the server’s agent certificate has the agent fingerprint of the peer of that session. The server closes it unless the client’s agent certificate has the fingerprint of the peer of one of its Local Peer-to-Peer sessions.
Each data-transport-start-request on a dedicated connection contains a session-binding: 32 bytes from the TLS exporter of the QUIC connection of the Local Peer-to-Peer session, with the label "EXPORTER-LP2P-q2q" and an empty context. The first one binds the connection to the Local Peer-to-Peer session whose exporter yields the same bytes. The server closes the connection if there is none, or if a later request carries a different session-binding.
Only the client opens sessions on a dedicated connection, and only the messages of this extension are sent on it, without agent-info or authentication messages. Everything else in this section applies unchanged. The client closes the connection when its last session closes, or when the Local Peer-to-Peer session closes.
Appendix A: OSP Extension Messages
The following messages are defined according to and as an extension to the Open Screen Network Protocol Messages. The agent-capability, data-frame, request, response and result types are defined in the Open Screen Application Protocol Messages.
Note: The type keys and capability IDs for these extensions are not officially registered yet. They will be registered as this specification matures.
agent-capability /= & ( data-channels : 1100 quic-transport : 1200 ) data-channel-encoding-id = & ( encoding-id-blob : 0 encoding-id-string : 1 encoding-id-array-buffer : 2 ) ; data-frame (type key 24) is the Open Screen Application Protocol message; ; only its encoding-id (0) and payload (4) keys are used. ; type key 1110 lp2p-session-request = { request ? 1 : text; requesting-origin 2 : advertisement-id ; advertisement-id } ; type key 1111 lp2p-session-response = { response 1 : lp2p-session-result ; result } lp2p-session-result = & ( result , authentication-required : 1110 ) ; type key 1112 lp2p-advertisements-request = { request } ; type key 1113 lp2p-advertisements-response = { response 1 : [ * lp2p-advertisement ] ; advertisements } lp2p-advertisement = { 0 : advertisement-id ; advertisement-id 1 : text; nickname } advertisement-id = bytes.size 16 ; type key 1101 data-channel-open-request = { request 1 : uint; channel-id 2 : text; label 3 : text; protocol } ; type key 1102 data-channel-open-response = { response 1 : & result ; result } ; type key 1201 data-transport-start-request = { request 1 : exchange-id ; exchange-id ? 2 : bytes.size 32 ; session-binding } ; type key 1202 data-transport-start-response = { response 1 : & result ; result } ; type key 1203 data-transport-stream = { 1 : exchange-id ; exchange-id } exchange-id = uint.lt 4611686018427387904 ; below 2^62 ; type key 1204 is reserved. ; type key 1205 data-transport-close = { 0 : uint.size 4 ; close-code 1 : text.size ( 0 .. 1024 ) ; reason } ; type key 1206 data-transport-drain = { }