@libp2p/websockets
Version:
JavaScript implementation of the WebSockets module that libp2p uses and that implements the interface-transport spec
144 lines • 5.22 kB
JavaScript
/**
* @packageDocumentation
*
* A [libp2p transport](https://docs.libp2p.io/concepts/transports/overview/) based on [WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API).
*
* @example
*
* ```TypeScript
* import { createLibp2p } from 'libp2p'
* import { webSockets } from '@libp2p/websockets'
* import { multiaddr } from '@multiformats/multiaddr'
*
* const node = await createLibp2p({
* transports: [
* webSockets()
* ]
* //... other config
* })
* await node.start()
*
* const ma = multiaddr('/dns4/example.com/tcp/9090/tls/ws')
* await node.dial(ma)
* ```
*/
import { transportSymbol, serviceCapabilities, ConnectionFailedError } from '@libp2p/interface';
import { multiaddrToUri as toUri } from '@multiformats/multiaddr-to-uri';
import { connect } from 'it-ws/client';
import pDefer from 'p-defer';
import { CustomProgressEvent } from 'progress-events';
import { raceSignal } from 'race-signal';
import * as filters from './filters.js';
import { createListener } from './listener.js';
import { socketToMaConn } from './socket-to-conn.js';
class WebSockets {
log;
init;
logger;
metrics;
components;
constructor(components, init = {}) {
this.log = components.logger.forComponent('libp2p:websockets');
this.logger = components.logger;
this.components = components;
this.init = init;
if (components.metrics != null) {
this.metrics = {
dialerEvents: components.metrics.registerCounterGroup('libp2p_websockets_dialer_events_total', {
label: 'event',
help: 'Total count of WebSockets dialer events by type'
})
};
}
}
[transportSymbol] = true;
[Symbol.toStringTag] = '@libp2p/websockets';
[serviceCapabilities] = [
'@libp2p/transport'
];
async dial(ma, options) {
this.log('dialing %s', ma);
options = options ?? {};
const socket = await this._connect(ma, options);
const maConn = socketToMaConn(socket, ma, {
logger: this.logger,
metrics: this.metrics?.dialerEvents
});
this.log('new outbound connection %s', maConn.remoteAddr);
const conn = await options.upgrader.upgradeOutbound(maConn, options);
this.log('outbound connection %s upgraded', maConn.remoteAddr);
return conn;
}
async _connect(ma, options) {
options?.signal?.throwIfAborted();
const cOpts = ma.toOptions();
this.log('dialing %s:%s', cOpts.host, cOpts.port);
const errorPromise = pDefer();
const rawSocket = connect(toUri(ma), this.init);
rawSocket.socket.addEventListener('error', () => {
// the WebSocket.ErrorEvent type doesn't actually give us any useful
// information about what happened
// https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/error_event
const err = new ConnectionFailedError(`Could not connect to ${ma.toString()}`);
this.log.error('connection error:', err);
this.metrics?.dialerEvents.increment({ error: true });
errorPromise.reject(err);
});
try {
options.onProgress?.(new CustomProgressEvent('websockets:open-connection'));
await raceSignal(Promise.race([rawSocket.connected(), errorPromise.promise]), options.signal);
}
catch (err) {
if (options.signal?.aborted) {
this.metrics?.dialerEvents.increment({ abort: true });
}
rawSocket.close()
.catch(err => {
this.log.error('error closing raw socket', err);
});
throw err;
}
this.log('connected %s', ma);
this.metrics?.dialerEvents.increment({ connect: true });
return rawSocket;
}
/**
* Creates a WebSockets listener. The provided `handler` function will be called
* anytime a new incoming Connection has been successfully upgraded via
* `upgrader.upgradeInbound`
*/
createListener(options) {
return createListener({
logger: this.logger,
events: this.components.events,
metrics: this.components.metrics
}, {
...this.init,
...options
});
}
/**
* Takes a list of `Multiaddr`s and returns only valid WebSockets addresses.
* By default, in a browser environment only DNS+WSS multiaddr is accepted,
* while in a Node.js environment DNS+{WS, WSS} multiaddrs are accepted.
*/
listenFilter(multiaddrs) {
multiaddrs = Array.isArray(multiaddrs) ? multiaddrs : [multiaddrs];
if (this.init?.filter != null) {
return this.init?.filter(multiaddrs);
}
return filters.all(multiaddrs);
}
/**
* Filter check for all Multiaddrs that this transport can dial
*/
dialFilter(multiaddrs) {
return this.listenFilter(multiaddrs);
}
}
export function webSockets(init = {}) {
return (components) => {
return new WebSockets(components, init);
};
}
//# sourceMappingURL=index.js.map