-
Notifications
You must be signed in to change notification settings - Fork 445
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge remote-tracking branch 'origin/main' into feat/webrtc-direct-in…
…-node-js
- Loading branch information
Showing
145 changed files
with
1,727 additions
and
421 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
{"packages/connection-encrypter-plaintext":"1.0.24","packages/connection-encrypter-tls":"1.0.12","packages/crypto":"4.1.2","packages/interface":"1.4.0","packages/interface-compliance-tests":"5.4.5","packages/interface-internal":"1.2.2","packages/kad-dht":"12.0.17","packages/keychain":"4.0.15","packages/libp2p":"1.6.0","packages/logger":"4.0.13","packages/metrics-devtools":"0.1.0","packages/metrics-prometheus":"3.0.24","packages/metrics-simple":"1.0.2","packages/multistream-select":"5.1.10","packages/peer-collections":"5.2.2","packages/peer-discovery-bootstrap":"10.0.24","packages/peer-discovery-mdns":"10.0.24","packages/peer-id":"4.1.2","packages/peer-id-factory":"4.1.2","packages/peer-record":"7.0.18","packages/peer-store":"10.0.19","packages/protocol-autonat":"1.0.21","packages/protocol-dcutr":"1.0.21","packages/protocol-echo":"1.0.7","packages/protocol-fetch":"1.0.18","packages/protocol-identify":"2.0.2","packages/protocol-perf":"3.0.24","packages/protocol-ping":"1.0.19","packages/pubsub":"9.0.19","packages/pubsub-floodsub":"9.0.20","packages/record":"4.0.2","packages/stream-multiplexer-mplex":"10.0.24","packages/transport-circuit-relay-v2":"1.0.24","packages/transport-tcp":"9.0.26","packages/transport-webrtc":"4.0.33","packages/transport-websockets":"8.0.24","packages/transport-webtransport":"4.0.32","packages/upnp-nat":"1.0.22","packages/utils":"5.4.2"} | ||
{"packages/connection-encrypter-plaintext":"1.0.25","packages/connection-encrypter-tls":"1.0.13","packages/crypto":"4.1.3","packages/interface":"1.4.1","packages/interface-compliance-tests":"5.4.6","packages/interface-internal":"1.2.3","packages/kad-dht":"12.0.18","packages/keychain":"4.0.16","packages/libp2p":"1.6.1","packages/logger":"4.0.14","packages/metrics-devtools":"0.1.1","packages/metrics-prometheus":"3.0.25","packages/metrics-simple":"1.0.3","packages/multistream-select":"5.1.11","packages/peer-collections":"5.2.3","packages/peer-discovery-bootstrap":"10.0.25","packages/peer-discovery-mdns":"10.0.25","packages/peer-id":"4.1.3","packages/peer-id-factory":"4.1.3","packages/peer-record":"7.0.19","packages/peer-store":"10.0.20","packages/protocol-autonat":"1.0.22","packages/protocol-dcutr":"1.0.22","packages/protocol-echo":"1.0.8","packages/protocol-fetch":"1.0.19","packages/protocol-identify":"2.0.3","packages/protocol-perf":"3.0.25","packages/protocol-ping":"1.0.20","packages/pubsub":"9.0.20","packages/pubsub-floodsub":"9.0.21","packages/record":"4.0.3","packages/stream-multiplexer-mplex":"10.0.25","packages/transport-circuit-relay-v2":"1.0.25","packages/transport-tcp":"9.0.27","packages/transport-webrtc":"4.0.34","packages/transport-websockets":"8.0.25","packages/transport-webtransport":"4.0.33","packages/upnp-nat":"1.1.0","packages/utils":"5.4.3"} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,351 @@ | ||
# Services | ||
|
||
Libp2p ships with very little functionality by default, this is to allow the greatest amount of flexibility and to ensure, for example, if you are deploying to web browsers you only pull in the code that your application needs. | ||
|
||
The functionality of your Libp2p node can be extended by configuring additional services. | ||
|
||
```ts | ||
import { createLibp2p } from 'libp2p' | ||
import { identify } from '@libp2p/identify' | ||
|
||
const node = await createLibp2p({ | ||
//.. other config here | ||
services: { | ||
identify: identify() | ||
} | ||
}) | ||
``` | ||
|
||
You can extend the capabilities of your node to suit your needs by writing custom services. | ||
|
||
## Writing custom services | ||
|
||
At it's simplest a service might look like this: | ||
|
||
```ts | ||
import { createLibp2p } from 'libp2p' | ||
|
||
// the service implementation | ||
class MyService { | ||
saySomething (): string { | ||
return 'Hello' | ||
} | ||
} | ||
|
||
// a function that returns a factory function | ||
function myService () { | ||
return () => { | ||
return new MyService() | ||
} | ||
} | ||
|
||
// create the libp2p node | ||
const node = await createLibp2p({ | ||
//.. other config here | ||
services: { | ||
myService: myService() | ||
} | ||
}) | ||
|
||
// invoke the service function | ||
console.info(node.services.myService.saySomething()) // 'Hello' | ||
``` | ||
|
||
### Accessing libp2p components | ||
|
||
Services can access internal libp2p components such as the address manger and connection manager by accepting an argument to the returned function. | ||
|
||
> [!IMPORTANT] | ||
> The key names of the `components` argument must match the field names of the internal [Components](https://github.com/libp2p/js-libp2p/blob/d1f1c2be78bd195f404e62627c2c9f545845e5f5/packages/libp2p/src/components.ts#L8-L28) class | ||
```ts | ||
import { createLibp2p } from 'libp2p' | ||
import type { ConnectionManager } from '@libp2p/interface-internal' | ||
|
||
// an interface that defines the minimal set of components the service requires | ||
interface MyServiceComponents { | ||
connectionManager: ConnectionManager | ||
} | ||
|
||
// the service implementation | ||
class MyService { | ||
private readonly components: MyServiceComponents | ||
|
||
constructor (components: MyServiceComponents) { | ||
this.components = components | ||
} | ||
|
||
saySomething (): string { | ||
return `There are ${this.components.connectionManager.getDialQueue().length} pending dials` | ||
} | ||
} | ||
|
||
// a function that returns a factory function | ||
function myService () { | ||
return (components: MyServiceComponents) => { | ||
return new MyService(components) | ||
} | ||
} | ||
|
||
// create the libp2p node | ||
const node = await createLibp2p({ | ||
//.. other config here | ||
services: { | ||
myService: myService() | ||
} | ||
}) | ||
|
||
// invoke the service function | ||
console.info(node.services.myService.saySomething()) // 'There are 0 pending dials' | ||
``` | ||
|
||
### Init args | ||
|
||
Your service can take arguments that allow for custom config. | ||
|
||
> [!TIP] | ||
> Make all arguments optional with sensible defaults | ||
```ts | ||
import { createLibp2p } from 'libp2p' | ||
import type { ConnectionManager } from '@libp2p/interface-internal' | ||
|
||
// an interface that defines the minimal set of components the service requires | ||
interface MyServiceComponents { | ||
connectionManager: ConnectionManager | ||
} | ||
|
||
// this interface defines the options this service supports | ||
interface MyServiceInit { | ||
message?: string | ||
} | ||
|
||
// the service implementation | ||
class MyService { | ||
private readonly components: MyServiceComponents | ||
private readonly message: string | ||
|
||
constructor (components: MyServiceComponents, init: MyServiceInit = {}) { | ||
this.components = components | ||
this.message = init.message ?? 'There are {} pending dials' | ||
} | ||
|
||
saySomething (): string { | ||
return this.message.replace('{}', `${this.components.connectionManager.getDialQueue().length}`) | ||
} | ||
} | ||
|
||
// a function that returns a factory function | ||
function myService (init: MyServiceInit) { | ||
return (components: MyServiceComponents) => { | ||
return new MyService(components, init) | ||
} | ||
} | ||
|
||
// create the libp2p node | ||
const node = await createLibp2p({ | ||
//.. other config here | ||
services: { | ||
myService: myService({ | ||
message: 'The queue is {} dials long' | ||
}) | ||
} | ||
}) | ||
|
||
// invoke the service function | ||
console.info(node.services.myService.saySomething()) // 'The queue is 0 dials long' | ||
``` | ||
|
||
## Service lifecycle | ||
|
||
Services that need to do async work during startup/shutdown can implement the [Startable](https://libp2p.github.io/js-libp2p/interfaces/_libp2p_interface.Startable.html) interface. | ||
|
||
It defines several methods that if defined, will be invoked when starting/stopping the node. | ||
|
||
All methods may return either `void` or `Promise<void>`. | ||
|
||
> [!WARNING] | ||
> If your functions are async, libp2p will wait for the returned promise to resolve before continuing which can increase startup/shutdown duration | ||
```ts | ||
import type { Startable } from '@libp2p/interface' | ||
|
||
class MyService implements Startable { | ||
async beforeStart (): Promise<void> { | ||
// optional, can be sync or async | ||
} | ||
|
||
async start (): Promise<void> { | ||
// can be sync or async | ||
} | ||
|
||
async afterStart (): Promise<void> { | ||
// optional, can be sync or async | ||
} | ||
|
||
async beforeStop (): Promise<void> { | ||
// optional, can be sync or async | ||
} | ||
|
||
async stop (): Promise<void> { | ||
// can be sync or async | ||
} | ||
|
||
async afterStop (): Promise<void> { | ||
// optional, can be sync or async | ||
} | ||
} | ||
``` | ||
|
||
### Depending on other services | ||
|
||
All configured services will be added to the `components` object, so you are able to access other custom services as well as libp2p internals. | ||
|
||
Defining it as part of your service components interface will cause TypeScript compilation errors if an instance is not present at the expected key in the service map. This should prevent misconfigurations if you are using TypeScript. | ||
|
||
If you do not depend on another service directly but still require it to be configured, see the next section on expressing service capabilities and dependencies. | ||
|
||
```ts | ||
import { createLibp2p } from 'libp2p' | ||
|
||
// first service | ||
class MyService { | ||
saySomething (): string { | ||
return 'Hello from myService' | ||
} | ||
} | ||
|
||
function myService () { | ||
return () => { | ||
return new MyService() | ||
} | ||
} | ||
|
||
// second service | ||
interface MyOtherServiceComponents { | ||
myService: MyService | ||
} | ||
|
||
class MyOtherService { | ||
private readonly components: MyOtherServiceComponents | ||
|
||
constructor (components: MyOtherServiceComponents) { | ||
this.components = components | ||
} | ||
|
||
speakToMyService (): string { | ||
return this.components.myService.saySomething() | ||
} | ||
} | ||
|
||
function myOtherService () { | ||
return (components: MyOtherServiceComponents) => { | ||
return new MyOtherService(components) | ||
} | ||
} | ||
|
||
// configure the node with both services | ||
const node = await createLibp2p({ | ||
// .. other config here | ||
services: { | ||
myService: myService(), | ||
myOtherService: myOtherService() | ||
} | ||
}) | ||
|
||
console.info(node.services.myOtherService.speakToMyService()) // 'Hello from myService' | ||
``` | ||
|
||
## Expressing service capabilities and dependencies | ||
|
||
If you have a dependency on the capabilities provided by another service without needing to directly invoke methods on it, you can inform libp2p by using symbol properties. | ||
|
||
libp2p will throw on construction if the dependencies of your service cannot be satisfied. | ||
|
||
This is useful if, for example, you configure a service that reacts to peer discovery in some way - you can define a requirement to have at least one peer discovery method configured. | ||
|
||
Similarly, if your service registers a network topology, these work by notifying topologies after [Identify](https://github.com/libp2p/specs/blob/master/identify/README.md) has run, so any service using topologies has an indirect dependency on `@libp2p/identify`. | ||
|
||
```ts | ||
import { createLibp2p } from 'libp2p' | ||
import { serviceCapabilities, serviceDependencies } from '@libp2p/interface' | ||
import type { Startable } from '@libp2p/interface' | ||
import type { Registrar } from '@libp2p/interface-internal' | ||
|
||
interface MyServiceComponents { | ||
registrar: Registrar | ||
} | ||
|
||
// This service registers a network topology. This functionality will not work | ||
// without the Identify protocol present, so it's defined as a dependency | ||
class MyService implements Startable { | ||
private readonly components: MyServiceComponents | ||
private topologyId?: string | ||
|
||
constructor (components: MyServiceComponents) { | ||
this.components = components | ||
} | ||
|
||
// this property is used as a human-friendly name for the service | ||
readonly [Symbol.toStringTag] = 'ServiceA' | ||
|
||
// this service provides these capabilities to the node | ||
readonly [serviceCapabilities]: string[] = [ | ||
'@my-org/my-capability' | ||
] | ||
|
||
// this service requires Identify to be configured on the current node | ||
readonly [serviceDependencies]: string[] = [ | ||
'@libp2p/identify' | ||
] | ||
|
||
async start (): Promise<void> { | ||
this.topologyId = await this.components.registrar.register('/my/protocol', { | ||
onConnect (peer, connection) { | ||
// handle connect | ||
} | ||
}) | ||
} | ||
|
||
stop (): void { | ||
if (this.topologyId != null) { | ||
this.components.registrar.unregister(this.topologyId) | ||
} | ||
} | ||
} | ||
|
||
function myService () { | ||
return (components: MyServiceComponents) => { | ||
return new MyService(components) | ||
} | ||
} | ||
|
||
// configure the node but omit identify | ||
const node = await createLibp2p({ | ||
// .. other config here | ||
services: { | ||
myService: myService() | ||
} | ||
}) // throws error because identify is not present | ||
``` | ||
|
||
### Frequently used dependencies | ||
|
||
These capabilities are provided by commonly used libp2p modules such as `@libp2p/identify`, `@chainsafe/libp2p-noise`, `@libp2p/webrtc` etc. | ||
|
||
Adding these strings to your service dependencies will cause starting libp2p to throw unless a service is configured to provide these capabilities. | ||
|
||
| Dependency | Implementations | Notes | | ||
| -------- | ------- | ------- | | ||
| `@libp2p/identify` | `@libp2p/identify` | You should declare this a as a dependency if your service uses the [Registrar](https://libp2p.github.io/js-libp2p/interfaces/_libp2p_interface_internal.Registrar.html) to register a network topology. | | ||
| `@libp2p/identify-push` | `@libp2p/identify` | | | ||
| `@libp2p/connection-encryption` | `@chainsafe/libp2p-noise`, `@libp2p/tls`, `@libp2p/plaintext` | | | ||
| `@libp2p/stream-multiplexing` | `@chainsafe/libp2p-yamux` | | | ||
| `@libp2p/content-routing` | `@libp2p/kad-dht` | | | ||
| `@libp2p/peer-routing` | `@libp2p/kad-dht` | | | ||
| `@libp2p/peer-discovery` | `@libp2p/kad-dht`, `@libp2p/bootstrap`, `@libp2p/mdns` | | | ||
| `@libp2p/keychain` | `@libp2p/keychain` | | | ||
| `@libp2p/metrics` | `@libp2p/prometheus-metrics`, `@libp2p/simple-metrics`, `@libp2p/devtool-metrics` | | | ||
| `@libp2p/transport` | `@libp2p/tcp`, `@libp2p/websockets`, `@libp2p/webrtc`, `@libp2p/webtransport`, `@libp2p/circuit-relay-v2` | | | ||
| `@libp2p/circuit-relay-v2-transport` | `@libp2p/circuit-relay-v2` | | | ||
| `@libp2p/nat-traversal` | `@libp2p/upnp-nat` | | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.