ssb-conn
Advanced tools
Comparing version 5.2.0 to 6.0.0
@@ -8,13 +8,15 @@ export declare class ConnScheduler { | ||
private closed; | ||
private loadedSocialGraph; | ||
private loadedHops; | ||
private lastMessageAt; | ||
private lastRotationAt; | ||
private hasScheduledAnUpdate; | ||
private socialGraph; | ||
private hops; | ||
constructor(ssb: any, config: any); | ||
private loadSocialGraph; | ||
private isCurrentlyDownloading; | ||
private weBlockThem; | ||
private weFollowThem; | ||
private isBlocked; | ||
private isNotBlocked; | ||
private isNotConnected; | ||
private maxWaitToConnect; | ||
private updateTheseConnections; | ||
private maintainConnections; | ||
private updateStagingNow; | ||
@@ -25,8 +27,9 @@ private updateHubNow; | ||
private removeDefunct; | ||
private populateWithSeeds; | ||
private setupPubDiscovery; | ||
private setupBluetoothDiscovery; | ||
private setupLanDiscovery; | ||
private setupRoomAttendantDiscovery; | ||
private setupPubDiscovery; | ||
private cleanUpDB; | ||
start: () => void; | ||
stop: () => void; | ||
} |
@@ -13,2 +13,3 @@ "use strict"; | ||
const ConnQuery = require("ssb-conn-query"); | ||
const { hasNoAttempts, hasOnlyFailedAttempts } = ConnQuery; | ||
const pull = require('pull-stream'); | ||
@@ -19,36 +20,24 @@ const Pausable = require('pull-pause'); | ||
const onNetwork = require('on-change-network-strict'); | ||
const hasNetwork = require('has-network2'); | ||
const hasNetworkRightNow = require('has-network2'); | ||
const Ref = require('ssb-ref'); | ||
const debug = require('debug')('ssb:conn:scheduler'); | ||
const SECONDS = 1e3; | ||
const MINUTES = 60e3; | ||
const HOUR = 60 * 60e3; | ||
const RANDOM_MULTIPLIER = 0.8 + Math.random() * 0.4; | ||
let lastCheck = 0; | ||
let lastValue = null; | ||
function hasNetworkDebounced() { | ||
function hasNetwork() { | ||
if (lastCheck + 1e3 < Date.now()) { | ||
lastCheck = Date.now(); | ||
lastValue = hasNetwork(); | ||
lastValue = hasNetworkRightNow(); | ||
} | ||
return lastValue; | ||
} | ||
function isOffline(p) { | ||
if (ip.isLoopback(p[1].host) || p[1].host == 'localhost') | ||
return false; | ||
else | ||
return !hasNetworkDebounced(); | ||
} | ||
const canBeConnected = (p) => !isOffline(p); | ||
function isLegacy(peer) { | ||
return hasSuccessfulAttempts(peer) && !hasPinged(peer); | ||
} | ||
function notRoom(peer) { | ||
return peer[1].type !== 'room'; | ||
} | ||
function notPub(peer) { | ||
return peer[1].type !== 'pub'; | ||
} | ||
function isDefunct(peer) { | ||
return peer[1].defunct === true; | ||
} | ||
function take(n) { | ||
return (arr) => arr.slice(0, Math.max(n, 0)); | ||
} | ||
function filter(condition) { | ||
return (arr) => arr.filter(condition); | ||
} | ||
function detectType(peer) { | ||
@@ -68,6 +57,8 @@ const [addr, data] = peer; | ||
return 'room'; | ||
if (data.type === 'room-endpoint') | ||
return 'room-attendant'; | ||
if (data.type === 'room-attendant') | ||
return 'room-attendant'; | ||
if (data.type === 'room-endpoint' || data.type === 'room-attendant') { | ||
if (data.alias) | ||
return 'room-attendant-alias'; | ||
else | ||
return 'room-attendant'; | ||
} | ||
if (data.source === 'local') | ||
@@ -95,20 +86,89 @@ return 'lan'; | ||
} | ||
const { passesExpBackoff, passesGroupDebounce, hasNoAttempts, hasOnlyFailedAttempts, hasPinged, hasSuccessfulAttempts, sortByStateChange, } = ConnQuery; | ||
function shufflePeers(peers) { | ||
return peers.sort(() => Math.random() - 0.5); | ||
const isNotLocalhost = (p) => !ip.isLoopback(p[1].host) && p[1].host !== 'localhost'; | ||
function isNotRoom(peer) { | ||
return peer[1].type !== 'room'; | ||
} | ||
const MINUTES = 60e3; | ||
const HOUR = 60 * 60e3; | ||
function isRoom(peer) { | ||
return peer[1].type === 'room'; | ||
} | ||
function isDefunct(peer) { | ||
return peer[1].defunct === true; | ||
} | ||
function filterOldExcess(excess) { | ||
return (peers) => { | ||
const WAIT_TIME = 2 * MINUTES * RANDOM_MULTIPLIER; | ||
return peers.filter((p) => Date.now() > p[1].hubUpdated + WAIT_TIME / excess); | ||
}; | ||
} | ||
function sortByOldestConnection(peers) { | ||
return peers.sort((a, b) => { | ||
return a[1].hubUpdated - b[1].hubUpdated; | ||
}); | ||
} | ||
function calculateCooldown(fullPercent, hops) { | ||
return (peers) => { | ||
return peers.map((peer) => { | ||
const [, data] = peer; | ||
const peerType = detectType(peer); | ||
const normalizedFullPercent = Math.max(0.1, fullPercent); | ||
const hop = hops[data.key]; | ||
const hopsCooldown = peerType === 'room' | ||
? 1 * SECONDS | ||
: hop === -1 | ||
? Infinity | ||
: hop === null || hop === void 0 | ||
? 30 * SECONDS | ||
: hop < 0 | ||
? -hop * 5 * SECONDS | ||
: hop * SECONDS; | ||
const retryCooldown = 4 * SECONDS + Math.min(64, data.failure || 0) ** 3 * 10 * SECONDS; | ||
let cooldown = (hopsCooldown + retryCooldown) * normalizedFullPercent; | ||
if (hasNoAttempts(peer)) | ||
cooldown *= 0.5; | ||
if (Math.random() < 0.3) | ||
cooldown *= 0.5; | ||
if (peerType === 'lan') | ||
cooldown *= 0.7; | ||
if (peerType === 'room-attendant') | ||
cooldown *= 0.8; | ||
if (hasOnlyFailedAttempts(peer)) | ||
cooldown *= 3; | ||
data.cooldown = cooldown; | ||
return peer; | ||
}); | ||
}; | ||
} | ||
function cooledDownEnough(peer) { | ||
var _a, _b; | ||
const [, data] = peer; | ||
const lastAttempt = (_b = (_a = data.stateChange) !== null && _a !== void 0 ? _a : data.hubUpdated) !== null && _b !== void 0 ? _b : 0; | ||
if (data.cooldown === undefined) | ||
return true; | ||
return Date.now() > lastAttempt + data.cooldown; | ||
} | ||
function sortByCooldownAscending(peers) { | ||
return peers.sort((a, b) => { | ||
const [, aData] = a; | ||
const [, bData] = b; | ||
if (aData.cooldown === undefined) | ||
return 1; | ||
if (bData.cooldown === undefined) | ||
return -1; | ||
return aData.cooldown - bData.cooldown; | ||
}); | ||
} | ||
let ConnScheduler = class ConnScheduler { | ||
constructor(ssb, config) { | ||
this.weBlockThem = ([_addr, data]) => { | ||
this.isBlocked = (peer) => { | ||
const [, data] = peer; | ||
if (!(data === null || data === void 0 ? void 0 : data.key)) | ||
return false; | ||
return this.socialGraph[data.key] === -1; | ||
return this.hops[data.key] === -1; | ||
}; | ||
this.weFollowThem = ([_addr, data]) => { | ||
if (!(data === null || data === void 0 ? void 0 : data.key)) | ||
return false; | ||
return this.socialGraph[data.key] > 0; | ||
this.isNotBlocked = (peer) => { | ||
return !this.isBlocked(peer); | ||
}; | ||
this.isNotConnected = (address) => { | ||
return !this.ssb.conn.hub().getState(address); | ||
}; | ||
this.start = () => { | ||
@@ -119,14 +179,3 @@ var _a, _b, _c; | ||
this.closed = false; | ||
for (let peer of this.ssb.conn.dbPeers()) { | ||
const [address, { source, type }] = peer; | ||
if (source === 'local' || | ||
source === 'bt' || | ||
type === 'lan' || | ||
type === 'bt') { | ||
this.ssb.conn.forget(address); | ||
} | ||
if (isDefunct(peer)) { | ||
this.removeDefunct(address); | ||
} | ||
} | ||
this.cleanUpDB(); | ||
this.ssbDB2Subscription = (_a = this.ssb.db) === null || _a === void 0 ? void 0 : _a.post((msg) => { | ||
@@ -138,22 +187,18 @@ if (msg.value.author !== this.ssb.id) { | ||
this.loadSocialGraph(); | ||
this.populateWithSeeds(); | ||
this.setupBluetoothDiscovery(); | ||
this.setupLanDiscovery(); | ||
this.setupRoomAttendantDiscovery(); | ||
this.setupPubDiscovery(); | ||
(_b = this.pubDiscoveryPausable) === null || _b === void 0 ? void 0 : _b.resume(); | ||
this.setupLanDiscovery(); | ||
this.setupBluetoothDiscovery(); | ||
this.intervalForUpdate = setInterval(() => this.updateSoon(), 2e3); | ||
if ((_c = this.intervalForUpdate) === null || _c === void 0 ? void 0 : _c.unref) | ||
this.intervalForUpdate.unref(); | ||
(_c = (_b = this.intervalForUpdate) === null || _b === void 0 ? void 0 : _b.unref) === null || _c === void 0 ? void 0 : _c.call(_b); | ||
onWakeup(() => { | ||
if (this.closed) | ||
return; | ||
this.ssb.conn.hub().reset(); | ||
if (!this.closed) | ||
this.ssb.conn.hub().reset(); | ||
}); | ||
onNetwork(() => { | ||
if (this.closed) | ||
return; | ||
this.ssb.conn.hub().reset(); | ||
if (!this.closed) | ||
this.ssb.conn.hub().reset(); | ||
}); | ||
pull(this.ssb.conn.hub().listen(), pull.filter((ev) => ev.type === 'disconnected'), pull.drain(() => this.updateSoon(200))); | ||
this.updateSoon(); | ||
this.updateNow(); | ||
}; | ||
@@ -176,20 +221,17 @@ this.stop = () => { | ||
this.lastMessageAt = 0; | ||
this.lastRotationAt = 0; | ||
this.hasScheduledAnUpdate = false; | ||
this.loadedSocialGraph = false; | ||
this.socialGraph = {}; | ||
this.loadedHops = false; | ||
this.hops = {}; | ||
} | ||
loadSocialGraph() { | ||
var _a, _b; | ||
if (!((_a = this.ssb.friends) === null || _a === void 0 ? void 0 : _a.graphStream)) { | ||
if (!((_a = this.ssb.friends) === null || _a === void 0 ? void 0 : _a.hopStream)) { | ||
debug('Warning: ssb-friends@5 is missing, scheduling is degraded'); | ||
this.loadedSocialGraph = true; | ||
this.loadedHops = true; | ||
return; | ||
} | ||
pull((_b = this.ssb.friends) === null || _b === void 0 ? void 0 : _b.graphStream({ live: true, old: true }), pull.drain((g) => { | ||
if (g[this.ssb.id]) { | ||
const prev = this.socialGraph; | ||
const updates = g[this.ssb.id]; | ||
this.socialGraph = { ...prev, ...updates }; | ||
} | ||
this.loadedSocialGraph = true; | ||
pull((_b = this.ssb.friends) === null || _b === void 0 ? void 0 : _b.hopStream({ live: true, old: true }), pull.drain((h) => { | ||
this.hops = { ...this.hops, ...h }; | ||
this.loadedHops = true; | ||
})); | ||
@@ -205,2 +247,4 @@ } | ||
return 30e3; | ||
case 'room-attendant-alias': | ||
return 30e3; | ||
case 'bt': | ||
@@ -214,25 +258,41 @@ return 60e3; | ||
} | ||
updateTheseConnections(test, opts) { | ||
maintainConnections(quota, isDesiredPeer, pool, isPeerRotatable, rotationPeriod) { | ||
const query = this.ssb.conn.query(); | ||
const peersUp = query.peersInConnection().filter(test); | ||
const peersDown = query.peersConnectable('db').filter(test); | ||
const { quota, backoffStep, backoffMax, groupMin } = opts; | ||
const peersUp = query.peersConnected().filter(isDesiredPeer); | ||
const peersDown = query | ||
.peersConnectable(pool) | ||
.filter(isDesiredPeer) | ||
.filter(this.isNotBlocked) | ||
.filter(isNotLocalhost) | ||
.filter(([, data]) => data.autoconnect !== false); | ||
const excess = peersUp.length > quota * 2 ? peersUp.length - quota : 0; | ||
const freeSlots = Math.max(quota - peersUp.length, 0); | ||
const fullPercent = 1 - freeSlots / quota; | ||
z(peersUp) | ||
.z(sortByStateChange) | ||
.z(filterOldExcess(excess)) | ||
.z(sortByOldestConnection) | ||
.z(take(excess)) | ||
.forEach(([addr]) => { | ||
const fuzzyPeriod = (120e3 * (0.5 + Math.random())) / excess; | ||
setTimeout(() => { | ||
.forEach(([addr]) => this.ssb.conn.disconnect(addr)); | ||
const ROTATION_PERIOD = (rotationPeriod * RANDOM_MULTIPLIER) / Math.sqrt(peersDown.length); | ||
if (freeSlots === 0 && | ||
peersDown.length > 0 && | ||
this.lastRotationAt + ROTATION_PERIOD < Date.now()) { | ||
z(peersUp) | ||
.z(filter(isPeerRotatable !== null && isPeerRotatable !== void 0 ? isPeerRotatable : (() => true))) | ||
.z(filter(([, data]) => { | ||
var _a, _b; | ||
const lastAttempt = (_b = (_a = data.stateChange) !== null && _a !== void 0 ? _a : data.hubUpdated) !== null && _b !== void 0 ? _b : 0; | ||
return lastAttempt + ROTATION_PERIOD < Date.now(); | ||
})) | ||
.z(sortByOldestConnection) | ||
.z(take(1)) | ||
.forEach(([addr]) => { | ||
this.lastRotationAt = Date.now(); | ||
this.ssb.conn.disconnect(addr); | ||
}, fuzzyPeriod); | ||
}); | ||
}); | ||
} | ||
z(peersDown) | ||
.z((peers) => peers.filter((p) => !this.weBlockThem(p))) | ||
.z((peers) => peers.filter(canBeConnected)) | ||
.z((peers) => peers.filter(([, data]) => data.autoconnect !== false)) | ||
.z(passesGroupDebounce(groupMin)) | ||
.z((peers) => peers.filter(passesExpBackoff(backoffStep, backoffMax))) | ||
.z((peers) => Math.random() <= 0.3 ? shufflePeers(peers) : sortByStateChange(peers)) | ||
.z(calculateCooldown(fullPercent, this.hops)) | ||
.z(filter(cooledDownEnough)) | ||
.z(sortByCooldownAscending) | ||
.z(take(freeSlots)) | ||
@@ -245,3 +305,3 @@ .forEach(([addr, data]) => this.ssb.conn.connect(addr, data)); | ||
.peersConnectable('db') | ||
.filter((p) => !this.weBlockThem(p)) | ||
.filter(this.isNotBlocked) | ||
.filter(([, data]) => data.autoconnect === false) | ||
@@ -252,3 +312,3 @@ .forEach(([addr, data]) => this.ssb.conn.stage(addr, data)); | ||
.peersConnectable('staging') | ||
.filter(this.weBlockThem) | ||
.filter(this.isBlocked) | ||
.forEach(([addr]) => this.ssb.conn.unstage(addr)); | ||
@@ -269,79 +329,15 @@ this.ssb.conn | ||
updateHubNow() { | ||
var _a; | ||
const conn = this.ssb.conn; | ||
if ((_a = this.config.seed) !== null && _a !== void 0 ? _a : true) { | ||
this.updateTheseConnections((p) => p[1].source === 'seed', { | ||
quota: 3, | ||
backoffStep: 2e3, | ||
backoffMax: 10 * MINUTES, | ||
groupMin: 1e3, | ||
}); | ||
} | ||
if (conn.query().peersInConnection().length === 0) { | ||
this.updateTheseConnections(() => true, { | ||
quota: 1, | ||
backoffStep: 1e3, | ||
backoffMax: 6e3, | ||
groupMin: 0, | ||
}); | ||
} | ||
this.updateTheseConnections((p) => p[1].type === 'room', { | ||
quota: 5, | ||
backoffStep: 5e3, | ||
backoffMax: 5 * MINUTES, | ||
groupMin: 5e3, | ||
}); | ||
this.updateTheseConnections((p) => notRoom(p) && hasPinged(p), { | ||
quota: 2, | ||
backoffStep: 10e3, | ||
backoffMax: 10 * MINUTES, | ||
groupMin: 5e3, | ||
}); | ||
this.updateTheseConnections((p) => notRoom(p) && hasNoAttempts(p), { | ||
quota: 2, | ||
backoffStep: 30e3, | ||
backoffMax: 30 * MINUTES, | ||
groupMin: 15e3, | ||
}); | ||
this.updateTheseConnections((p) => notRoom(p) && hasOnlyFailedAttempts(p), { | ||
quota: 3, | ||
backoffStep: 1 * MINUTES, | ||
backoffMax: 3 * HOUR, | ||
groupMin: 5 * MINUTES, | ||
}); | ||
this.updateTheseConnections((p) => notRoom(p) && isLegacy(p), { | ||
quota: 1, | ||
backoffStep: 4 * MINUTES, | ||
backoffMax: 3 * HOUR, | ||
groupMin: 5 * MINUTES, | ||
}); | ||
z(conn | ||
this.maintainConnections(5, isRoom, 'db', (p) => p[1].onlineCount === 0, 2 * MINUTES); | ||
this.maintainConnections(4, isNotRoom, 'dbAndStaging', null, 2 * HOUR); | ||
this.ssb.conn | ||
.query() | ||
.peersConnectable('staging') | ||
.filter(this.weFollowThem) | ||
.filter(notPub)) | ||
.z(take(3 - | ||
conn | ||
.query() | ||
.peersInConnection() | ||
.filter(this.weFollowThem) | ||
.filter(notPub).length)) | ||
.forEach(([addr, data]) => conn.connect(addr, data)); | ||
conn | ||
.query() | ||
.peersInConnection() | ||
.filter(this.weBlockThem) | ||
.forEach(([addr]) => conn.disconnect(addr)); | ||
conn | ||
.filter(this.isBlocked) | ||
.forEach(([addr]) => this.ssb.conn.disconnect(addr)); | ||
this.ssb.conn | ||
.query() | ||
.peersInConnection() | ||
.filter((p) => conn.hub().getState(p[0]) === 'connecting') | ||
.filter((p) => this.ssb.conn.hub().getState(p[0]) === 'connecting') | ||
.filter((p) => p[1].stateChange + this.maxWaitToConnect(p) < Date.now()) | ||
.forEach(([addr]) => conn.disconnect(addr)); | ||
conn | ||
.query() | ||
.peersConnected() | ||
.filter((p) => p[1].type !== 'bt' && p[1].type !== 'lan') | ||
.filter((p) => p[1].stateChange + 0.5 * HOUR < Date.now()) | ||
.forEach(([addr]) => conn.disconnect(addr)); | ||
.forEach(([addr]) => this.ssb.conn.disconnect(addr)); | ||
} | ||
@@ -353,4 +349,6 @@ updateNow() { | ||
return; | ||
if (!this.loadedSocialGraph) | ||
if (!this.loadedHops) | ||
return; | ||
if (!hasNetwork()) | ||
return; | ||
this.updateStagingNow(); | ||
@@ -376,12 +374,76 @@ this.updateHubNow(); | ||
} | ||
populateWithSeeds() { | ||
setupBluetoothDiscovery() { | ||
var _a; | ||
const seeds = (_a = this.config.seeds) !== null && _a !== void 0 ? _a : []; | ||
(Array.isArray(seeds) ? seeds : [seeds]).filter(Boolean).forEach((addr) => { | ||
const key = Ref.getKeyFromAddress(addr); | ||
this.ssb.conn.remember(addr, { key, source: 'seed' }); | ||
}); | ||
if (!((_a = this.ssb.bluetooth) === null || _a === void 0 ? void 0 : _a.nearbyScuttlebuttDevices)) { | ||
debug('Warning: ssb-bluetooth is missing, scheduling is degraded'); | ||
return; | ||
} | ||
pull(this.ssb.bluetooth.nearbyScuttlebuttDevices(1000), pull.drain(({ discovered }) => { | ||
if (this.closed) | ||
return; | ||
for (const btPeer of discovered) { | ||
const addr = `bt:${btPeer.remoteAddress.split(':').join('')}` + | ||
'~' + | ||
`shs:${btPeer.id.replace(/^\@/, '').replace(/\.ed25519$/, '')}`; | ||
const data = { | ||
type: 'bt', | ||
note: btPeer.displayName, | ||
key: btPeer.id, | ||
}; | ||
if (this.isNotBlocked([addr, data]) && this.isNotConnected(addr)) { | ||
this.ssb.conn.stage(addr, data); | ||
this.updateSoon(100); | ||
} | ||
} | ||
})); | ||
} | ||
setupLanDiscovery() { | ||
var _a, _b; | ||
if (!((_a = this.ssb.lan) === null || _a === void 0 ? void 0 : _a.start) || !((_b = this.ssb.lan) === null || _b === void 0 ? void 0 : _b.discoveredPeers)) { | ||
debug('Warning: ssb-lan is missing, scheduling is degraded'); | ||
return; | ||
} | ||
pull(this.ssb.lan.discoveredPeers(), pull.drain(({ address, verified }) => { | ||
const key = Ref.getKeyFromAddress(address); | ||
if (!key) | ||
return; | ||
const data = { | ||
type: 'lan', | ||
key, | ||
verified, | ||
}; | ||
if (this.isNotBlocked([address, data]) && | ||
this.isNotConnected(address)) { | ||
this.ssb.conn.stage(address, data); | ||
this.updateSoon(100); | ||
} | ||
})); | ||
this.ssb.lan.start(); | ||
} | ||
setupRoomAttendantDiscovery() { | ||
var _a; | ||
const timer = setTimeout(() => { | ||
var _a; | ||
if (!((_a = this.ssb.roomClient) === null || _a === void 0 ? void 0 : _a.discoveredAttendants)) { | ||
debug('Warning: ssb-room-client@2 is missing, scheduling is degraded'); | ||
return; | ||
} | ||
pull(this.ssb.roomClient.discoveredAttendants(), pull.drain((attendant) => { | ||
const addr = attendant.address; | ||
const data = { | ||
type: 'room-attendant', | ||
key: attendant.key, | ||
room: attendant.room, | ||
roomName: attendant.roomName, | ||
}; | ||
if (this.isNotBlocked([addr, data]) && this.isNotConnected(addr)) { | ||
this.ssb.conn.stage(addr, data); | ||
this.updateSoon(100); | ||
} | ||
})); | ||
}, 100); | ||
(_a = timer === null || timer === void 0 ? void 0 : timer.unref) === null || _a === void 0 ? void 0 : _a.call(timer); | ||
} | ||
setupPubDiscovery() { | ||
var _a, _b; | ||
var _a, _b, _c; | ||
if (((_a = this.config.conn) === null || _a === void 0 ? void 0 : _a.populatePubs) === false) | ||
@@ -393,3 +455,3 @@ return; | ||
} | ||
setTimeout(() => { | ||
const timer = setTimeout(() => { | ||
var _a, _b; | ||
@@ -407,3 +469,3 @@ if (this.closed) | ||
const key = Ref.getKeyFromAddress(address); | ||
if (this.weBlockThem([address, { key }])) { | ||
if (this.isBlocked([address, { key }])) { | ||
this.ssb.conn.forget(address); | ||
@@ -437,53 +499,30 @@ } | ||
}, 1000); | ||
(_c = timer === null || timer === void 0 ? void 0 : timer.unref) === null || _c === void 0 ? void 0 : _c.call(timer); | ||
} | ||
setupBluetoothDiscovery() { | ||
var _a; | ||
if (!((_a = this.ssb.bluetooth) === null || _a === void 0 ? void 0 : _a.nearbyScuttlebuttDevices)) { | ||
debug('Warning: ssb-bluetooth is missing, scheduling is degraded'); | ||
return; | ||
cleanUpDB() { | ||
const roomsWithMembership = new Set(); | ||
for (let peer of this.ssb.conn.dbPeers()) { | ||
const [address, { source, type, membership }] = peer; | ||
if (source === 'local' || | ||
source === 'bt' || | ||
type === 'lan' || | ||
type === 'bt') { | ||
this.ssb.conn.forget(address); | ||
} | ||
if (isDefunct(peer)) { | ||
this.removeDefunct(address); | ||
} | ||
if (type === 'room' && membership) { | ||
roomsWithMembership.add(address); | ||
} | ||
} | ||
pull(this.ssb.bluetooth.nearbyScuttlebuttDevices(1000), pull.drain(({ discovered }) => { | ||
if (this.closed) | ||
return; | ||
for (const btPeer of discovered) { | ||
const address = `bt:${btPeer.remoteAddress.split(':').join('')}` + | ||
'~' + | ||
`shs:${btPeer.id.replace(/^\@/, '').replace(/\.ed25519$/, '')}`; | ||
const data = { | ||
type: 'bt', | ||
note: btPeer.displayName, | ||
key: btPeer.id, | ||
}; | ||
if (this.weFollowThem([address, data])) { | ||
this.ssb.conn.connect(address, data); | ||
for (let [address, data] of this.ssb.conn.dbPeers()) { | ||
if (data.type === 'room-endpoint' || data.type === 'room-attendant') { | ||
if (data.alias && | ||
data.roomAddress && | ||
roomsWithMembership.has(data.roomAddress)) { | ||
this.ssb.conn.forget(address); | ||
} | ||
else { | ||
this.ssb.conn.stage(address, data); | ||
} | ||
} | ||
})); | ||
} | ||
setupLanDiscovery() { | ||
var _a, _b; | ||
if (!((_a = this.ssb.lan) === null || _a === void 0 ? void 0 : _a.start) || !((_b = this.ssb.lan) === null || _b === void 0 ? void 0 : _b.discoveredPeers)) { | ||
debug('Warning: ssb-lan is missing, scheduling is degraded'); | ||
return; | ||
} | ||
pull(this.ssb.lan.discoveredPeers(), pull.drain(({ address, verified }) => { | ||
const key = Ref.getKeyFromAddress(address); | ||
if (!key) | ||
return; | ||
const data = { | ||
type: 'lan', | ||
key, | ||
verified, | ||
}; | ||
if (this.weFollowThem([address, data])) { | ||
this.ssb.conn.connect(address, data); | ||
} | ||
else { | ||
this.ssb.conn.stage(address, data); | ||
} | ||
})); | ||
this.ssb.lan.start(); | ||
} | ||
@@ -490,0 +529,0 @@ }; |
@@ -8,3 +8,3 @@ import { FeedId } from 'ssb-typescript'; | ||
port?: number; | ||
source: 'seed' | 'pub' | 'manual' | 'friends' | 'local' | 'dht' | 'bt' | 'stored'; | ||
source: 'pub' | 'manual' | 'friends' | 'local' | 'dht' | 'bt' | 'stored'; | ||
error?: string; | ||
@@ -28,4 +28,2 @@ state?: undefined | 'connecting' | 'connected' | 'disconnecting'; | ||
export interface Config { | ||
seed?: boolean; | ||
seeds?: Array<string> | string; | ||
conn?: { | ||
@@ -43,3 +41,10 @@ autostart: boolean; | ||
}) => CallableFunction; | ||
hopStream: (opts: { | ||
old: boolean; | ||
live: boolean; | ||
}) => CallableFunction; | ||
}>; | ||
readonly roomClient?: Readonly<{ | ||
discoveredAttendants: () => CallableFunction; | ||
}>; | ||
readonly bluetooth?: Readonly<{ | ||
@@ -46,0 +51,0 @@ nearbyScuttlebuttDevices: (x: number) => CallableFunction; |
{ | ||
"name": "ssb-conn", | ||
"description": "SSB plugin for establishing and managing peer connections", | ||
"version": "5.2.0", | ||
"version": "6.0.0", | ||
"homepage": "https://github.com/staltz/ssb-conn", | ||
@@ -6,0 +6,0 @@ "main": "lib/index.js", |
@@ -99,3 +99,3 @@ <div style="text-align:center" align="center"> | ||
๐น `hubUpdated?: number`: Unix timestamp for when this data object was last updated in ConnHub | ||
๐น `hubUpdated?: number`: Unix timestamp for when this data object was last updated in ConnHub, which means the last time it was connected or attempted | ||
@@ -106,9 +106,9 @@ ๐น `stagingBirth?: number`: Unix timestamp for when this peer was added to ConnStaging | ||
๐น `autoconnect?: boolean`: indicates whether this peer should be considered for connection in the scheduler | ||
๐น `autoconnect?: boolean`: indicates whether this peer should be considered for automatic connection in the scheduler. By the default this field is considered `true` whenever it's undefined, and if you want opt-out of automatic connections for this peer (thus delegating it to a manual choice by the user), then set it to `false`. | ||
๐น `failure?: number`: typically in ConnDB, this is the number of connection errors since the last successful connection | ||
๐น `failure?: number`: typically stored in ConnDB, this is the number of connection errors since the last successful connection | ||
๐น `duration?: object`: typically in ConnDB, this is a [statistics](https://www.npmjs.com/package/statistics) object to measure the duration of connection with this peer | ||
๐น `duration?: object`: typically stored in ConnDB, this is a [statistics](https://www.npmjs.com/package/statistics) object to measure the duration of connection with this peer | ||
๐น `ping?: object`: typically in ConnDB, this is [statistics](https://www.npmjs.com/package/statistics) object of various ping health measurements | ||
๐น `ping?: object`: typically stored in ConnDB, this is [statistics](https://www.npmjs.com/package/statistics) object of various ping health measurements | ||
@@ -119,3 +119,3 @@ ๐น `pool?: 'db' | 'hub' | 'staging'`: this only appears in ConnQuery APIs, and indicates from which pool (ConnDB or ConnHub or ConnStaging) was this peer picked | ||
๐ธ `room?: string`: (only if `type = 'room-endpoint'`) the public key of the [room](https://github.com/staltz/ssb-room) server where this peer is in | ||
๐ธ `room?: string`: (only if `type = 'room-attendant'`) the public key of the [room](https://github.com/staltz/ssb-room) server where this peer is in | ||
@@ -289,14 +289,18 @@ ๐ธ `onlineCount?: number`: (only if `type = 'room'`) the number of room endpoints currently connected to this room | ||
- Listen to a stream of Bluetooth nearby devices, and `stage` them | ||
- Listen to a stream of peers online in Rooms, and `stage` them | ||
**Periodic connections/disconnections:** | ||
- With (5sec) exponential backoff, try to connect to at most 5 room servers | ||
- With (10sec) exponential backoff, try to connect to at most 2 non-room peers that we have connected successfully before | ||
- With (30sec) exponential backoff, try to connect to at most 2 non-room peers that we have never with connected before | ||
- With (1min) exponential backoff, try to connect to at most 3 non-room peers that have we always failed to connect with | ||
- With (4min) exponential backoff, try to connect to at most 1 non-room peer that seem to run a legacy version of the gossip plugin | ||
- Try to maintain connections with 5 room servers | ||
- If we're connected to more than 5, then after some minutes we'll start disconnecting from some rooms | ||
- Try to maintain connections with 4 non-room peers (pubs, room attendants, LAN peers, etc) | ||
- If we're connected to more than 4, then after some minutes we'll start disconnecting from some | ||
- The lower the hops distance of the peer, the higher priority they receive | ||
- The more connection failures the peer has presented, the lower the priority | ||
- Room attendants and LAN peers have slight priority over pubs | ||
- After we've been connected to a peer for many minutes, disconnect from them | ||
and try to connect to different peers, to encourage diversity of connections | ||
In none of the cases above shall we connect to a peer that we block. In addition to the above, the following actions happen automatically every (approximately) 1 second: | ||
- Connect to (at most 3) staged peers we follow | ||
- Disconnect from connected peers that have just been blocked by us | ||
@@ -306,2 +310,3 @@ - Disconnect from peers that have been connected with us for more than 30min | ||
- "Too long" means 30sec for LAN peers | ||
- "Too long" means 30sec for Room attendants | ||
- "Too long" means 1min for Bluetooth peers | ||
@@ -320,2 +325,3 @@ - "Too long" means 5min for DHT invite peers | ||
- Remove database entries for any LAN or Bluetooth peers (these are rediscovered just-in-time) | ||
- Remove room alias addresses if those aliases are in rooms where I have membership | ||
@@ -402,3 +408,3 @@ **Other events:** | ||
- All "pools" (DB, Hub, Staging) are key-value pairs `[address, dataObject]` | ||
- Make scheduling easily customizable but provide an opinionated default | ||
- Make scheduling logic easily swappable but provide an opinionated default | ||
@@ -405,0 +411,0 @@ <ul></ul> |
60815
1040
412