mirror of https://codeberg.org/pzp/pzp-net.git
use multiaddr instead of multiserver addresses
This commit is contained in:
parent
4101e023cd
commit
c2d1adb19e
|
@ -2,10 +2,11 @@ const debug = require('debug')('ppppp:net:connections')
|
||||||
const createNotify = require('pull-notify')
|
const createNotify = require('pull-notify')
|
||||||
const run = require('promisify-tuple')
|
const run = require('promisify-tuple')
|
||||||
const IP = require('ip')
|
const IP = require('ip')
|
||||||
|
const Multiaddr = require('./multiaddr')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('./index').RpcConnectListener} RpcConnectListener
|
* @typedef {import('./index').RpcConnectListener} RpcConnectListener
|
||||||
* @typedef {import('./index').Address} Address
|
* @typedef {import('./index').Multiaddr} Multiaddr
|
||||||
* @typedef {import('./index').RPC} RPC
|
* @typedef {import('./index').RPC} RPC
|
||||||
* @typedef {import('./index').Peer} Peer
|
* @typedef {import('./index').Peer} Peer
|
||||||
* @typedef {import('./infos').Info} Info
|
* @typedef {import('./infos').Info} Info
|
||||||
|
@ -17,7 +18,7 @@ const IP = require('ip')
|
||||||
* | 'connecting-failed'
|
* | 'connecting-failed'
|
||||||
* | 'disconnecting'
|
* | 'disconnecting'
|
||||||
* | 'disconnected';
|
* | 'disconnected';
|
||||||
* address: Address;
|
* multiaddr: Multiaddr;
|
||||||
* pubkey: string | undefined;
|
* pubkey: string | undefined;
|
||||||
* details?: any;
|
* details?: any;
|
||||||
* }} ConnectionEvent
|
* }} ConnectionEvent
|
||||||
|
@ -37,12 +38,12 @@ class Connections {
|
||||||
#closed
|
#closed
|
||||||
/** @type {NotifyEvent} */
|
/** @type {NotifyEvent} */
|
||||||
#notifyEvent
|
#notifyEvent
|
||||||
/** @type {Map<Address, RPC>} */
|
/** @type {Map<Multiaddr, RPC>} */
|
||||||
#rpcs
|
#rpcs
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used only to schedule a connect when a disconnect is in progress.
|
* Used only to schedule a connect when a disconnect is in progress.
|
||||||
* @type {Set<Address>}
|
* @type {Set<Multiaddr>}
|
||||||
*/
|
*/
|
||||||
#connectRetries
|
#connectRetries
|
||||||
|
|
||||||
|
@ -62,7 +63,7 @@ class Connections {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Address} address
|
* @param {string} address
|
||||||
* @returns {Info['inferredType']}
|
* @returns {Info['inferredType']}
|
||||||
*/
|
*/
|
||||||
static inferPeerType(address) {
|
static inferPeerType(address) {
|
||||||
|
@ -111,28 +112,29 @@ class Connections {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {(address: Address, rpc: RPC, weAreClient: boolean) => void}
|
* @type {(address: string, rpc: RPC, weAreClient: boolean) => void}
|
||||||
*/
|
*/
|
||||||
#prepareConnectedRPC = (address, rpc, weAreClient) => {
|
#prepareConnectedRPC = (address, rpc, weAreClient) => {
|
||||||
const initiator = weAreClient ? 'we' : 'they'
|
const initiator = weAreClient ? 'we' : 'they'
|
||||||
debug('Connected to %s, %s initiated it', address, initiator)
|
const multiaddr = Multiaddr.fromMs(address)
|
||||||
|
debug('Connected to %s, %s initiated it', multiaddr, initiator)
|
||||||
|
|
||||||
const pubkey = Connections.extractSHSEPubkey(address)
|
const pubkey = Connections.extractSHSEPubkey(address)
|
||||||
this.#rpcs.set(address, rpc)
|
this.#rpcs.set(multiaddr, rpc)
|
||||||
rpc.once('closed', () => {
|
rpc.once('closed', () => {
|
||||||
debug('Disconnected from %s', address)
|
debug('Disconnected from %s', multiaddr)
|
||||||
this.#rpcs.delete(address)
|
this.#rpcs.delete(multiaddr)
|
||||||
this.#infos.update(address, { state: 'disconnected' })
|
this.#infos.update(multiaddr, { state: 'disconnected' })
|
||||||
this.#notifyEvent({ type: 'disconnected', address, pubkey })
|
this.#notifyEvent({ type: 'disconnected', multiaddr, pubkey })
|
||||||
this.#infos.emit()
|
this.#infos.emit()
|
||||||
})
|
})
|
||||||
|
|
||||||
const state = /**@type {Info['state']}*/ ('connected')
|
const state = /**@type {Info['state']}*/ ('connected')
|
||||||
const inferredType = Connections.inferPeerType(address)
|
const inferredType = Connections.inferPeerType(address)
|
||||||
this.#infos.update(address, { state, inferredType })
|
this.#infos.update(multiaddr, { state, inferredType })
|
||||||
this.#notifyEvent({
|
this.#notifyEvent({
|
||||||
type: state,
|
type: state,
|
||||||
address,
|
multiaddr,
|
||||||
pubkey,
|
pubkey,
|
||||||
details: { rpc, weAreClient },
|
details: { rpc, weAreClient },
|
||||||
})
|
})
|
||||||
|
@ -140,37 +142,38 @@ class Connections {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} address
|
* @param {Multiaddr} multiaddr
|
||||||
* @returns {Promise<RPC>}
|
* @returns {Promise<RPC>}
|
||||||
*/
|
*/
|
||||||
async connect(address) {
|
async connect(multiaddr) {
|
||||||
this.#assertNotClosed()
|
this.#assertNotClosed()
|
||||||
|
|
||||||
const prevInfo = this.#infos.get(address)
|
const address = Multiaddr.toMs(multiaddr)
|
||||||
|
const prevInfo = this.#infos.get(multiaddr)
|
||||||
switch (prevInfo?.state ?? 'disconnected') {
|
switch (prevInfo?.state ?? 'disconnected') {
|
||||||
case 'connected': {
|
case 'connected': {
|
||||||
const rpc = this.#rpcs.get(address)
|
const rpc = this.#rpcs.get(multiaddr)
|
||||||
if (!rpc) {
|
if (!rpc) {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
throw new Error(`Failed to connect to ${address} due to inconsistent internal state`);
|
throw new Error(`Failed to connect to ${multiaddr} due to inconsistent internal state`);
|
||||||
}
|
}
|
||||||
return rpc
|
return rpc
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'disconnecting': {
|
case 'disconnecting': {
|
||||||
// If disconnecting, schedule a connect() after disconnection completed
|
// If disconnecting, schedule a connect() after disconnection completed
|
||||||
this.#connectRetries.add(address)
|
this.#connectRetries.add(multiaddr)
|
||||||
// note: control flow should fall through below!
|
// note: control flow should fall through below!
|
||||||
}
|
}
|
||||||
case 'connecting': {
|
case 'connecting': {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let timeout = 100
|
let timeout = 100
|
||||||
const checkAgain = () => {
|
const checkAgain = () => {
|
||||||
const rpc = this.#rpcs.get(address)
|
const rpc = this.#rpcs.get(multiaddr)
|
||||||
if (rpc) resolve(rpc)
|
if (rpc) resolve(rpc)
|
||||||
else if (timeout > 5 * 60e3) {
|
else if (timeout > 5 * 60e3) {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
reject(new Error(`Failed to connect to ${address} after waiting a long time`))
|
reject(new Error(`Failed to connect to ${multiaddr} after waiting a long time`))
|
||||||
} else {
|
} else {
|
||||||
timeout *= 2
|
timeout *= 2
|
||||||
setTimeout(checkAgain, timeout)
|
setTimeout(checkAgain, timeout)
|
||||||
|
@ -181,20 +184,20 @@ class Connections {
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'disconnected': {
|
case 'disconnected': {
|
||||||
debug('Connecting to %s', address)
|
debug('Connecting to %s', multiaddr)
|
||||||
const state = /**@type {Info['state']}*/ ('connecting')
|
const state = /**@type {Info['state']}*/ ('connecting')
|
||||||
const pubkey = Connections.extractSHSEPubkey(address)
|
const pubkey = Connections.extractSHSEPubkey(address)
|
||||||
this.#infos.update(address, { state })
|
this.#infos.update(multiaddr, { state })
|
||||||
this.#notifyEvent({ type: state, address, pubkey })
|
this.#notifyEvent({ type: state, multiaddr, pubkey })
|
||||||
this.#infos.emit()
|
this.#infos.emit()
|
||||||
|
|
||||||
const [err, rpc] = await run(this.#peer.connect)(address)
|
const [err, rpc] = await run(this.#peer.connect)(address)
|
||||||
if (err) {
|
if (err) {
|
||||||
this.#infos.update(address, { state: 'disconnected' })
|
this.#infos.update(multiaddr, { state: 'disconnected' })
|
||||||
debug('Failed to connect to %s because: %s', address, err.message)
|
debug('Failed to connect to %s because: %s', multiaddr, err.message)
|
||||||
this.#notifyEvent({
|
this.#notifyEvent({
|
||||||
type: 'connecting-failed',
|
type: 'connecting-failed',
|
||||||
address,
|
multiaddr,
|
||||||
pubkey,
|
pubkey,
|
||||||
details: err,
|
details: err,
|
||||||
})
|
})
|
||||||
|
@ -202,15 +205,15 @@ class Connections {
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
|
|
||||||
const concurrentInfo = this.#infos.get(address)
|
const concurrentInfo = this.#infos.get(multiaddr)
|
||||||
if (!concurrentInfo || concurrentInfo.state !== 'connected') {
|
if (!concurrentInfo || concurrentInfo.state !== 'connected') {
|
||||||
this.#prepareConnectedRPC(address, rpc, true)
|
this.#prepareConnectedRPC(address, rpc, true)
|
||||||
return rpc
|
return rpc
|
||||||
} else {
|
} else {
|
||||||
const rpc2 = this.#rpcs.get(address)
|
const rpc2 = this.#rpcs.get(multiaddr)
|
||||||
if (!rpc2) {
|
if (!rpc2) {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
throw new Error(`Failed to connect to ${address} due to inconsistent internal state`);
|
throw new Error(`Failed to connect to ${multiaddr} due to inconsistent internal state`);
|
||||||
}
|
}
|
||||||
return rpc2
|
return rpc2
|
||||||
}
|
}
|
||||||
|
@ -218,20 +221,21 @@ class Connections {
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
debug('Unexpected control flow, peer %s has bad state %o', address, prevInfo)
|
debug('Unexpected control flow, peer %s has bad state %o', multiaddr, prevInfo)
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
throw new Error(`Unexpected control flow, peer ${address} has bad state "${prevInfo?.state ?? '?'}"`)
|
throw new Error(`Unexpected control flow, peer ${multiaddr} has bad state "${prevInfo?.state ?? '?'}"`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Address} address
|
* @param {Multiaddr} multiaddr
|
||||||
* @returns {Promise<boolean>}
|
* @returns {Promise<boolean>}
|
||||||
*/
|
*/
|
||||||
async disconnect(address) {
|
async disconnect(multiaddr) {
|
||||||
this.#assertNotClosed()
|
this.#assertNotClosed()
|
||||||
const prevInfo = this.#infos.get(address)
|
const address = Multiaddr.toMs(multiaddr)
|
||||||
|
const prevInfo = this.#infos.get(multiaddr)
|
||||||
if (!prevInfo || prevInfo?.state === 'disconnected') return false
|
if (!prevInfo || prevInfo?.state === 'disconnected') return false
|
||||||
if (prevInfo.state === 'disconnecting') return false
|
if (prevInfo.state === 'disconnecting') return false
|
||||||
|
|
||||||
|
@ -241,7 +245,7 @@ class Connections {
|
||||||
rpc = await new Promise((resolve) => {
|
rpc = await new Promise((resolve) => {
|
||||||
let timeout = 100
|
let timeout = 100
|
||||||
const checkAgain = () => {
|
const checkAgain = () => {
|
||||||
const rpc = this.#rpcs.get(address)
|
const rpc = this.#rpcs.get(multiaddr)
|
||||||
if (rpc) resolve(rpc)
|
if (rpc) resolve(rpc)
|
||||||
else {
|
else {
|
||||||
timeout *= 2
|
timeout *= 2
|
||||||
|
@ -252,20 +256,20 @@ class Connections {
|
||||||
checkAgain()
|
checkAgain()
|
||||||
})
|
})
|
||||||
} else if (prevInfo.state === 'connected') {
|
} else if (prevInfo.state === 'connected') {
|
||||||
const maybeRPC = this.#rpcs.get(address)
|
const maybeRPC = this.#rpcs.get(multiaddr)
|
||||||
if (!maybeRPC) {
|
if (!maybeRPC) {
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
throw new Error(`Failed to disconnect from ${address} due to inconsistent internal state`);
|
throw new Error(`Failed to disconnect from ${multiaddr} due to inconsistent internal state`);
|
||||||
} else {
|
} else {
|
||||||
rpc = maybeRPC
|
rpc = maybeRPC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug('Disconnecting from %s', address)
|
debug('Disconnecting from %s', multiaddr)
|
||||||
const state = /**@type {Info['state']}*/ ('disconnecting')
|
const state = /**@type {Info['state']}*/ ('disconnecting')
|
||||||
const pubkey = Connections.extractSHSEPubkey(address)
|
const pubkey = Connections.extractSHSEPubkey(address)
|
||||||
this.#infos.update(address, { state })
|
this.#infos.update(multiaddr, { state })
|
||||||
this.#notifyEvent({ type: state, address, pubkey })
|
this.#notifyEvent({ type: state, multiaddr, pubkey })
|
||||||
this.#infos.emit()
|
this.#infos.emit()
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
await run(rpc.close)(true)
|
await run(rpc.close)(true)
|
||||||
|
@ -273,9 +277,9 @@ class Connections {
|
||||||
|
|
||||||
// Re-connect because while disconnect() was running,
|
// Re-connect because while disconnect() was running,
|
||||||
// someone called connect()
|
// someone called connect()
|
||||||
if (this.#connectRetries.has(address)) {
|
if (this.#connectRetries.has(multiaddr)) {
|
||||||
this.#connectRetries.delete(address)
|
this.#connectRetries.delete(multiaddr)
|
||||||
this.connect(address)
|
this.connect(multiaddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
20
lib/glue.js
20
lib/glue.js
|
@ -3,7 +3,7 @@ const stats = require('statistics')
|
||||||
const ping = require('pull-ping')
|
const ping = require('pull-ping')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('./index').Address} Address
|
* @typedef {import('./index').Multiaddr} Multiaddr
|
||||||
* @typedef {import('./index').RPC} RPC
|
* @typedef {import('./index').RPC} RPC
|
||||||
* @typedef {import('./index').Peer} Peer
|
* @typedef {import('./index').Peer} Peer
|
||||||
* @typedef {import('./connections')} Connections
|
* @typedef {import('./connections')} Connections
|
||||||
|
@ -19,13 +19,13 @@ const PROGRAM_STARTUP = Date.now()
|
||||||
*/
|
*/
|
||||||
function glue(infos, connections) {
|
function glue(infos, connections) {
|
||||||
/**
|
/**
|
||||||
* @param {Address} address
|
* @param {Multiaddr} multiaddr
|
||||||
* @param {RPC} rpc
|
* @param {RPC} rpc
|
||||||
*/
|
*/
|
||||||
function setupPing(address, rpc) {
|
function setupPing(multiaddr, rpc) {
|
||||||
const PING_TIMEOUT = 5 * 6e4 // 5 minutes
|
const PING_TIMEOUT = 5 * 6e4 // 5 minutes
|
||||||
const pp = ping({ serve: true, timeout: PING_TIMEOUT }, () => {})
|
const pp = ping({ serve: true, timeout: PING_TIMEOUT }, () => {})
|
||||||
infos.updateStats(address, () => ({
|
infos.updateStats(multiaddr, () => ({
|
||||||
ping: {
|
ping: {
|
||||||
rtt: pp.rtt,
|
rtt: pp.rtt,
|
||||||
skew: pp.skew,
|
skew: pp.skew,
|
||||||
|
@ -36,7 +36,7 @@ function glue(infos, connections) {
|
||||||
rpc.net.ping({ timeout: PING_TIMEOUT }, (err, _) => {
|
rpc.net.ping({ timeout: PING_TIMEOUT }, (err, _) => {
|
||||||
console.warn('remote peer ping err', err)
|
console.warn('remote peer ping err', err)
|
||||||
// if (err?.name === 'TypeError') {
|
// if (err?.name === 'TypeError') {
|
||||||
// infos.update(address, {stats: {ping: {fail: true}}});
|
// infos.update(multiaddr, {stats: {ping: {fail: true}}});
|
||||||
// }
|
// }
|
||||||
}),
|
}),
|
||||||
pp
|
pp
|
||||||
|
@ -47,7 +47,7 @@ function glue(infos, connections) {
|
||||||
* @param {Event} ev
|
* @param {Event} ev
|
||||||
*/
|
*/
|
||||||
function onConnectingFailed(ev) {
|
function onConnectingFailed(ev) {
|
||||||
infos.updateStats(ev.address, (prevStats) => ({
|
infos.updateStats(ev.multiaddr, (prevStats) => ({
|
||||||
failure: (prevStats?.failure ?? 0) + 1,
|
failure: (prevStats?.failure ?? 0) + 1,
|
||||||
stateChange: Date.now(),
|
stateChange: Date.now(),
|
||||||
duration: stats(prevStats?.duration, 0),
|
duration: stats(prevStats?.duration, 0),
|
||||||
|
@ -58,18 +58,18 @@ function glue(infos, connections) {
|
||||||
* @param {Event} ev
|
* @param {Event} ev
|
||||||
*/
|
*/
|
||||||
function onConnected(ev) {
|
function onConnected(ev) {
|
||||||
infos.updateStats(ev.address, () => ({
|
infos.updateStats(ev.multiaddr, () => ({
|
||||||
stateChange: Date.now(),
|
stateChange: Date.now(),
|
||||||
failure: 0,
|
failure: 0,
|
||||||
}))
|
}))
|
||||||
if (ev.details.weAreClient) setupPing(ev.address, ev.details.rpc)
|
if (ev.details.weAreClient) setupPing(ev.multiaddr, ev.details.rpc)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Event} ev
|
* @param {Event} ev
|
||||||
*/
|
*/
|
||||||
function bumpStateChange(ev) {
|
function bumpStateChange(ev) {
|
||||||
infos.updateStats(ev.address, () => ({
|
infos.updateStats(ev.multiaddr, () => ({
|
||||||
stateChange: Date.now(),
|
stateChange: Date.now(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -78,7 +78,7 @@ function glue(infos, connections) {
|
||||||
* @param {Event} ev
|
* @param {Event} ev
|
||||||
*/
|
*/
|
||||||
function onDisconnected(ev) {
|
function onDisconnected(ev) {
|
||||||
infos.updateStats(ev.address, (prevStats) => ({
|
infos.updateStats(ev.multiaddr, (prevStats) => ({
|
||||||
stateChange: Date.now(),
|
stateChange: Date.now(),
|
||||||
duration: stats(
|
duration: stats(
|
||||||
prevStats?.duration,
|
prevStats?.duration,
|
||||||
|
|
42
lib/index.js
42
lib/index.js
|
@ -9,7 +9,7 @@ const glue = require('./glue')
|
||||||
/**
|
/**
|
||||||
* @typedef {import('pull-stream').Duplex<unknown, unknown>} Duplex
|
* @typedef {import('pull-stream').Duplex<unknown, unknown>} Duplex
|
||||||
* @typedef {import('./connections').ConnectionEvent} ConnectionEvent
|
* @typedef {import('./connections').ConnectionEvent} ConnectionEvent
|
||||||
* @typedef {string} Address
|
* @typedef {`/${string}`} Multiaddr
|
||||||
* @typedef {(rpc: RPC, weAreClient: boolean) => void} RpcConnectListener
|
* @typedef {(rpc: RPC, weAreClient: boolean) => void} RpcConnectListener
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
* shse: {pubkey: string};
|
* shse: {pubkey: string};
|
||||||
|
@ -24,12 +24,14 @@ const glue = require('./glue')
|
||||||
* multiserver: {
|
* multiserver: {
|
||||||
* parse(address: string): any
|
* parse(address: string): any
|
||||||
* },
|
* },
|
||||||
|
* }} Peer
|
||||||
|
* @typedef {Peer & {
|
||||||
|
* stream: {address: string}
|
||||||
* net: {
|
* net: {
|
||||||
* ping(opts: {timeout: number}, cb: CB<void>): Duplex;
|
* ping(opts: {timeout: number}, cb: CB<void>): Duplex;
|
||||||
* listen(): import('pull-stream').Source<ConnectionEvent>;
|
* listen(): import('pull-stream').Source<ConnectionEvent>;
|
||||||
* },
|
* },
|
||||||
* }} Peer
|
* }} RPC
|
||||||
* @typedef {Peer & {stream: {address: string}}} RPC
|
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
* global: {
|
* global: {
|
||||||
* path?: string
|
* path?: string
|
||||||
|
@ -97,56 +99,56 @@ function initNet(peer, config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Address} address
|
* @param {Multiaddr} multiaddr
|
||||||
* @param {Partial<Info>} info
|
* @param {Partial<Info>} info
|
||||||
*/
|
*/
|
||||||
function stage(address, info) {
|
function stage(multiaddr, info) {
|
||||||
if (info.state) throw new Error('Cannot stage peer info with "state" field')
|
if (info.state) throw new Error('Cannot stage peer info with "state" field')
|
||||||
if (infos.has(address)) {
|
if (infos.has(multiaddr)) {
|
||||||
return false
|
return false
|
||||||
} else {
|
} else {
|
||||||
infos.update(address, info)
|
infos.update(multiaddr, info)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Address} address
|
* @param {Multiaddr} multiaddr
|
||||||
* @param {CB<RPC>=} cb
|
* @param {CB<RPC>=} cb
|
||||||
*/
|
*/
|
||||||
function connect(address, cb) {
|
function connect(multiaddr, cb) {
|
||||||
connections.connect(address).then(
|
connections.connect(multiaddr).then(
|
||||||
(result) => cb?.(null, result),
|
(result) => cb?.(null, result),
|
||||||
(err) => cb?.(err)
|
(err) => cb?.(err)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Address} address
|
* @param {Multiaddr} multiaddr
|
||||||
* @param {CB<boolean>=} cb
|
* @param {CB<boolean>=} cb
|
||||||
*/
|
*/
|
||||||
function disconnect(address, cb) {
|
function disconnect(multiaddr, cb) {
|
||||||
return connections.disconnect(address).then(
|
return connections.disconnect(multiaddr).then(
|
||||||
(result) => cb?.(null, result),
|
(result) => cb?.(null, result),
|
||||||
(err) => cb?.(err)
|
(err) => cb?.(err)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Address} address
|
* @param {Multiaddr} multiaddr
|
||||||
*/
|
*/
|
||||||
function forget(address) {
|
function forget(multiaddr) {
|
||||||
disconnect(address, () => {
|
disconnect(multiaddr, () => {
|
||||||
infos.remove(address)
|
infos.remove(multiaddr)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Address} address
|
* @param {Multiaddr} multiaddr
|
||||||
* @param {Info} info
|
* @param {Info} info
|
||||||
*/
|
*/
|
||||||
function updateInfo(address, info) {
|
function updateInfo(multiaddr, info) {
|
||||||
infos.update(address, info)
|
infos.update(multiaddr, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
function listen() {
|
function listen() {
|
||||||
|
|
54
lib/infos.js
54
lib/infos.js
|
@ -9,7 +9,7 @@ const Obz = require('obz')
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('./index').Address} Address
|
* @typedef {import('./index').Multiaddr} Multiaddr
|
||||||
* @typedef {import('./stats').StatsInfo} StatsInfo
|
* @typedef {import('./stats').StatsInfo} StatsInfo
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
* state: 'connected' | 'disconnected' | 'connecting' | 'disconnecting',
|
* state: 'connected' | 'disconnected' | 'connecting' | 'disconnecting',
|
||||||
|
@ -19,11 +19,11 @@ const Obz = require('obz')
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class Infos {
|
class Infos {
|
||||||
/** @type {Map<Address, Info>} */
|
/** @type {Map<Multiaddr, Info>} */
|
||||||
#map
|
#map
|
||||||
/** @type {ReturnType<createNotify>} */
|
/** @type {ReturnType<createNotify>} */
|
||||||
#notify
|
#notify
|
||||||
/** @type {Obz<Address>} */
|
/** @type {Obz<Multiaddr>} */
|
||||||
#onStatsUpdated
|
#onStatsUpdated
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -33,76 +33,76 @@ class Infos {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Address} address
|
* @param {Multiaddr} multiaddr
|
||||||
* @returns {Info | undefined}
|
* @returns {Info | undefined}
|
||||||
*/
|
*/
|
||||||
get(address) {
|
get(multiaddr) {
|
||||||
return this.#map.get(address)
|
return this.#map.get(multiaddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Address} address
|
* @param {Multiaddr} multiaddr
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
has(address) {
|
has(multiaddr) {
|
||||||
return this.#map.has(address)
|
return this.#map.has(multiaddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Address} address
|
* @param {Multiaddr} multiaddr
|
||||||
* @param {Partial<Info>} info
|
* @param {Partial<Info>} info
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
update(address, info) {
|
update(multiaddr, info) {
|
||||||
const hasNewStats = !!info.stats
|
const hasNewStats = !!info.stats
|
||||||
const prevInfo = this.#map.get(address)
|
const prevInfo = this.#map.get(multiaddr)
|
||||||
if (prevInfo) {
|
if (prevInfo) {
|
||||||
for (const key of Object.keys(info)) {
|
for (const key of Object.keys(info)) {
|
||||||
const k = /**@type {keyof Info}*/ (key)
|
const k = /**@type {keyof Info}*/ (key)
|
||||||
if (typeof info[k] === 'undefined') delete info[k]
|
if (typeof info[k] === 'undefined') delete info[k]
|
||||||
}
|
}
|
||||||
this.#map.set(address, { ...prevInfo, ...info })
|
this.#map.set(multiaddr, { ...prevInfo, ...info })
|
||||||
} else if (!info.state) {
|
} else if (!info.state) {
|
||||||
this.#map.set(address, { ...info, state: 'disconnected' })
|
this.#map.set(multiaddr, { ...info, state: 'disconnected' })
|
||||||
} else {
|
} else {
|
||||||
this.#map.set(address, /**@type {Info}*/ (info))
|
this.#map.set(multiaddr, /**@type {Info}*/ (info))
|
||||||
}
|
}
|
||||||
if (hasNewStats) {
|
if (hasNewStats) {
|
||||||
this.#onStatsUpdated.set(address)
|
this.#onStatsUpdated.set(multiaddr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Address} address
|
* @param {Multiaddr} multiaddr
|
||||||
* @param {(prevStats: Partial<Info['stats']>) => Partial<Info['stats']>} getStats
|
* @param {(prevStats: Partial<Info['stats']>) => Partial<Info['stats']>} getStats
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
updateStats(address, getStats) {
|
updateStats(multiaddr, getStats) {
|
||||||
const prevInfo = this.#map.get(address)
|
const prevInfo = this.#map.get(multiaddr)
|
||||||
if (!prevInfo) return
|
if (!prevInfo) return
|
||||||
this.#map.set(address, {
|
this.#map.set(multiaddr, {
|
||||||
...prevInfo,
|
...prevInfo,
|
||||||
stats: {
|
stats: {
|
||||||
...prevInfo?.stats,
|
...prevInfo?.stats,
|
||||||
...getStats(prevInfo?.stats),
|
...getStats(prevInfo?.stats),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
this.#onStatsUpdated.set(address)
|
this.#onStatsUpdated.set(multiaddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Parameters<Obz<Address>>[0]} listener
|
* @param {Parameters<Obz<Multiaddr>>[0]} listener
|
||||||
*/
|
*/
|
||||||
onStatsUpdated(listener) {
|
onStatsUpdated(listener) {
|
||||||
return this.#onStatsUpdated(listener)
|
return this.#onStatsUpdated(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Address} address
|
* @param {Multiaddr} multiaddr
|
||||||
*/
|
*/
|
||||||
remove(address) {
|
remove(multiaddr) {
|
||||||
this.#map.delete(address)
|
this.#map.delete(multiaddr)
|
||||||
this.#onStatsUpdated.set(address)
|
this.#onStatsUpdated.set(multiaddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
size() {
|
size() {
|
||||||
|
@ -118,7 +118,7 @@ class Infos {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {pull.Source<[Address, Info]>}
|
* @returns {pull.Source<[Multiaddr, Info]>}
|
||||||
*/
|
*/
|
||||||
liveEntries() {
|
liveEntries() {
|
||||||
return pullConcat([
|
return pullConcat([
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
const IP = require('ip')
|
||||||
|
|
||||||
|
const Multiaddr = {
|
||||||
|
/**
|
||||||
|
* Converts (legacy) [multiserver](https://github.com/ssbc/multiserver-address)
|
||||||
|
* addresses to [multiaddr](https://multiformats.io/multiaddr/) (modern).
|
||||||
|
* @param {string} msaddr
|
||||||
|
* @returns {`/${string}`}
|
||||||
|
*/
|
||||||
|
fromMs(msaddr) {
|
||||||
|
const [msTransport, msTransform] = msaddr.split('~')
|
||||||
|
const [label1, host, port] = msTransport.split(':')
|
||||||
|
|
||||||
|
const hostFormat = IP.isV4Format(host)
|
||||||
|
? 'ip4'
|
||||||
|
: IP.isV6Format('ipv6')
|
||||||
|
? 'ip6'
|
||||||
|
: 'dns'
|
||||||
|
const transport = label1 === 'net' ? 'tcp' : label1 === 'ws' ? 'ws' : null
|
||||||
|
if (!transport) throw new Error(`Unknown transport "${label1}"`)
|
||||||
|
const soFar = `${hostFormat}/${host}/${transport}/${port}`
|
||||||
|
|
||||||
|
if (msTransform) {
|
||||||
|
const [label2, pubkey, token] = msTransform.split(':')
|
||||||
|
if (label2 !== 'shse') throw new Error(`Unknown transform "${label2}"`)
|
||||||
|
if (token) {
|
||||||
|
return `/${soFar}/shse/${pubkey}.${token}`
|
||||||
|
} else {
|
||||||
|
return `/${soFar}/shse/${pubkey}`
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return `/${soFar}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts [multiaddr](https://multiformats.io/multiaddr/) (modern) to
|
||||||
|
* [multiserver](https://github.com/ssbc/multiserver-address) address (legacy).
|
||||||
|
* @param {`/${string}`} multiaddr
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
toMs(multiaddr) {
|
||||||
|
if (!multiaddr.startsWith('/')) {
|
||||||
|
// prettier-ignore
|
||||||
|
throw new Error(`Invalid multiaddr "${multiaddr}"`)
|
||||||
|
}
|
||||||
|
const [, , host, transport, port, transform, cred] = multiaddr.split('/')
|
||||||
|
const label1 =
|
||||||
|
transport === 'tcp' ? 'net' : transport === 'ws' ? 'ws' : null
|
||||||
|
if (!label1) throw new Error(`Unknown transport "${transport}"`)
|
||||||
|
const soFar = `${label1}:${host}:${port}`
|
||||||
|
if (transform) {
|
||||||
|
// prettier-ignore
|
||||||
|
if (transform !== 'shse') throw new Error(`Unknown transform "${transform}"`)
|
||||||
|
const [pubkey, token] = cred.split('.')
|
||||||
|
if (token) {
|
||||||
|
return `${soFar}~shse:${pubkey}:${token}`
|
||||||
|
} else {
|
||||||
|
return `${soFar}~shse:${pubkey}`
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return soFar
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Multiaddr
|
18
lib/stats.js
18
lib/stats.js
|
@ -4,7 +4,7 @@ const debug = require('debug')('ppppp:net:stats')
|
||||||
const atomic = require('atomic-file-rw')
|
const atomic = require('atomic-file-rw')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('./index').Address} Address
|
* @typedef {import('./index').Multiaddr} Multiaddr
|
||||||
* @typedef {import('./infos')} Infos
|
* @typedef {import('./infos')} Infos
|
||||||
* @typedef {import('statistics').Statistics} Statistics
|
* @typedef {import('statistics').Statistics} Statistics
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
|
@ -42,7 +42,7 @@ const SelfHealingJSONCodec = {
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @param {any} input
|
* @param {any} input
|
||||||
* @returns {Record<string, any>}
|
* @returns {Record<`/${string}`, any>}
|
||||||
*/
|
*/
|
||||||
decode(input) {
|
decode(input) {
|
||||||
if (!input) return {}
|
if (!input) return {}
|
||||||
|
@ -114,8 +114,10 @@ class Stats {
|
||||||
return
|
return
|
||||||
} else if (fileContents) {
|
} else if (fileContents) {
|
||||||
const vals = SelfHealingJSONCodec.decode(fileContents)
|
const vals = SelfHealingJSONCodec.decode(fileContents)
|
||||||
for (const [address, statsInfo] of Object.entries(vals)) {
|
for (const [multiaddr, statsInfo] of Object.entries(vals)) {
|
||||||
this.#infos.update(address, { stats: statsInfo })
|
this.#infos.update(/**@type {`/${string}`}*/ (multiaddr), {
|
||||||
|
stats: statsInfo,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
this.#loadedResolve(true)
|
this.#loadedResolve(true)
|
||||||
debug('Loaded conn.json into ConnDB in memory')
|
debug('Loaded conn.json into ConnDB in memory')
|
||||||
|
@ -180,10 +182,10 @@ class Stats {
|
||||||
*/
|
*/
|
||||||
#writeToDisk(cb) {
|
#writeToDisk(cb) {
|
||||||
debug(`Begun serializing and writing ${Stats.FILENAME}`)
|
debug(`Begun serializing and writing ${Stats.FILENAME}`)
|
||||||
const record = /**@type {Record<Address, StatsInfo>}*/ ({})
|
const record = /**@type {Record<Multiaddr, StatsInfo>}*/ ({})
|
||||||
for (let [address, info] of this.#infos.entries()) {
|
for (let [multiaddr, info] of this.#infos.entries()) {
|
||||||
if (info.stats) {
|
if (info.stats) {
|
||||||
record[address] = info.stats
|
record[multiaddr] = info.stats
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const json = SelfHealingJSONCodec.encode(record)
|
const json = SelfHealingJSONCodec.encode(record)
|
||||||
|
@ -198,7 +200,7 @@ class Stats {
|
||||||
this.#closed = true
|
this.#closed = true
|
||||||
this.#cancelScheduleWrite()
|
this.#cancelScheduleWrite()
|
||||||
this.#writeToDisk()
|
this.#writeToDisk()
|
||||||
;/**@type {any}*/ (this).#infos = void 0
|
;/**@type {any}*/ (this.#infos) = void 0
|
||||||
debug('Closed the Stats instance')
|
debug('Closed the Stats instance')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"net:staltz.com:8008~noauth": {
|
"/dns/staltz.com/tcp/8008": {
|
||||||
"source": "stored"
|
"source": "stored"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"net:staltz.com:8008~noauth": {
|
"/dns/staltz.com/tcp/8008": {
|
||||||
"duration": {
|
"duration": {
|
||||||
"mean": 0,
|
"mean": 0,
|
||||||
"stdev": 0,
|
"stdev": 0,
|
||||||
|
|
|
@ -5,8 +5,8 @@ const Path = require('node:path')
|
||||||
const p = require('node:util').promisify
|
const p = require('node:util').promisify
|
||||||
const { createPeerMock } = require('./util')
|
const { createPeerMock } = require('./util')
|
||||||
|
|
||||||
const TEST_ADDR =
|
const PUBKEY = 'EqTMFv7zm8hpPyAkj789qdJgqtz81AEbcinpAs24RRUC'
|
||||||
'net:localhost:9752~shse:EqTMFv7zm8hpPyAkj789qdJgqtz81AEbcinpAs24RRUC'
|
const TEST_ADDR = `/ip4/127.0.0.1/tcp/9752/shse/${PUBKEY}`
|
||||||
|
|
||||||
test('Glueing together stats with connections', async (t) => {
|
test('Glueing together stats with connections', async (t) => {
|
||||||
await t.test('stage() is ignored when peer already connected', async () => {
|
await t.test('stage() is ignored when peer already connected', async () => {
|
||||||
|
|
|
@ -5,7 +5,7 @@ const pull = require('pull-stream')
|
||||||
const { createPeer } = require('./util')
|
const { createPeer } = require('./util')
|
||||||
|
|
||||||
const PUBKEY = 'EqTMFv7zm8hpPyAkj789qdJgqtz81AEbcinpAs24RRUC'
|
const PUBKEY = 'EqTMFv7zm8hpPyAkj789qdJgqtz81AEbcinpAs24RRUC'
|
||||||
const TEST_ADDR = `net:localhost:9752~shse:${PUBKEY}`
|
const TEST_ADDR = `/ip4/127.0.0.1/tcp/9752/shse/${PUBKEY}`
|
||||||
|
|
||||||
test('net', async (t) => {
|
test('net', async (t) => {
|
||||||
await t.test('connect() rejects given unreachable address', async () => {
|
await t.test('connect() rejects given unreachable address', async () => {
|
||||||
|
@ -68,11 +68,11 @@ test('net', async (t) => {
|
||||||
++i
|
++i
|
||||||
if (i === 1) {
|
if (i === 1) {
|
||||||
assert.equal(ev.type, 'connecting', 'event.type ok')
|
assert.equal(ev.type, 'connecting', 'event.type ok')
|
||||||
assert.equal(ev.address, TEST_ADDR, 'event.address ok')
|
assert.equal(ev.multiaddr, TEST_ADDR, 'event.address ok')
|
||||||
assert.equal(ev.pubkey, PUBKEY, 'event.pubkey ok')
|
assert.equal(ev.pubkey, PUBKEY, 'event.pubkey ok')
|
||||||
} else if (i === 2) {
|
} else if (i === 2) {
|
||||||
assert.equal(ev.type, 'connecting-failed', 'event.type ok')
|
assert.equal(ev.type, 'connecting-failed', 'event.type ok')
|
||||||
assert.equal(ev.address, TEST_ADDR, 'event.address ok')
|
assert.equal(ev.multiaddr, TEST_ADDR, 'event.address ok')
|
||||||
assert.ok(ev.details, 'event.details ok')
|
assert.ok(ev.details, 'event.details ok')
|
||||||
assert.equal(ev.details.code, 'ECONNREFUSED', 'event.details err')
|
assert.equal(ev.details.code, 'ECONNREFUSED', 'event.details err')
|
||||||
queueMicrotask(resolve)
|
queueMicrotask(resolve)
|
||||||
|
|
|
@ -24,7 +24,7 @@ test('Stats', async (t) => {
|
||||||
const entriesAfter = Array.from(infos.entries())
|
const entriesAfter = Array.from(infos.entries())
|
||||||
assert.equal(entriesAfter.length, 1, 'after loaded(), there is data')
|
assert.equal(entriesAfter.length, 1, 'after loaded(), there is data')
|
||||||
const [address, info] = entriesAfter[0]
|
const [address, info] = entriesAfter[0]
|
||||||
assert.equal(address, 'net:staltz.com:8008~noauth', 'the address looks ok')
|
assert.equal(address, '/dns/staltz.com/tcp/8008', 'the address looks ok')
|
||||||
assert.equal(info.stats.source, 'stored', 'the info looks ok')
|
assert.equal(info.stats.source, 'stored', 'the info looks ok')
|
||||||
|
|
||||||
stats.close()
|
stats.close()
|
||||||
|
@ -64,7 +64,7 @@ test('Stats', async (t) => {
|
||||||
|
|
||||||
const entries = Array.from(infos.entries())
|
const entries = Array.from(infos.entries())
|
||||||
assert.equal(entries.length === 1, true, 'stats has one entry')
|
assert.equal(entries.length === 1, true, 'stats has one entry')
|
||||||
assert.equal(entries[0][0], 'net:staltz.com:8008~noauth', 'entry addr ok')
|
assert.equal(entries[0][0], '/dns/staltz.com/tcp/8008', 'entry addr ok')
|
||||||
assert.ok(entries[0][1].stats.duration, 'entry stats.duration ok')
|
assert.ok(entries[0][1].stats.duration, 'entry stats.duration ok')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue