Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into feat/webrtc-direct-in…
Browse files Browse the repository at this point in the history
…-node-js
  • Loading branch information
achingbrain committed Jun 14, 2024
2 parents 40c198a + 94cac11 commit b572498
Show file tree
Hide file tree
Showing 145 changed files with 1,727 additions and 421 deletions.
2 changes: 1 addition & 1 deletion .release-please-manifest.json
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"}
351 changes: 351 additions & 0 deletions doc/SERVICES.md
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` | |
15 changes: 15 additions & 0 deletions packages/connection-encrypter-plaintext/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,21 @@
* devDependencies
* @libp2p/interface-compliance-tests bumped from ^5.3.0 to ^5.3.1

## [1.0.25](https://github.com/libp2p/js-libp2p/compare/plaintext-v1.0.24...plaintext-v1.0.25) (2024-06-07)


### Dependencies

* bump aegir from 42.2.11 to 43.0.1 ([#2571](https://github.com/libp2p/js-libp2p/issues/2571)) ([757fb26](https://github.com/libp2p/js-libp2p/commit/757fb2674f0a3e06fd46d3ff63f7f461c32d47d2))
* The following workspace dependencies were updated
* dependencies
* @libp2p/interface bumped from ^1.4.0 to ^1.4.1
* @libp2p/peer-id bumped from ^4.1.2 to ^4.1.3
* devDependencies
* @libp2p/interface-compliance-tests bumped from ^5.4.5 to ^5.4.6
* @libp2p/logger bumped from ^4.0.13 to ^4.0.14
* @libp2p/peer-id-factory bumped from ^4.1.2 to ^4.1.3

## [1.0.24](https://github.com/libp2p/js-libp2p/compare/plaintext-v1.0.23...plaintext-v1.0.24) (2024-05-17)


Expand Down
Loading

0 comments on commit b572498

Please sign in to comment.