@kronos-integration/service-swarm
Version:
manage a set of remote services
201 lines (170 loc) • 5.04 kB
JavaScript
import { pipeline } from "node:stream";
import Hyperswarm from "hyperswarm";
import { Decode, Encode } from "length-prefix-framed-stream";
import { prepareAttributesDefinitions, private_key_attribute } from "pacc";
import { Service } from "@kronos-integration/service";
import { Topic } from "./topic.mjs";
import { TopicEndpoint } from "./topic-endpoint.mjs";
import { PeersEndpoint } from "./peers-endpoint.mjs";
/**
* Swarm detecting sync service.
*/
export class ServiceSwarm extends Service {
_topics; // = new Map();
_topicsByName; // = new Map();
/**
* @return {string} 'swarm'
*/
static get name() {
return "swarm";
}
static attributes = prepareAttributesDefinitions(
{
server: {
needsRestart: true,
default: false,
type: "boolean"
},
client: {
needsRestart: true,
default: false,
type: "boolean"
},
dht: {
description: "well known dht addresses",
needsRestart: true,
type: "object"
},
maxPeers: {
description: "total amount of peers that this peer will connect to",
default: 10,
needsRestart: true,
type: "integer"
},
key: {
...private_key_attribute,
description: "topic initial key",
needsRestart: true,
type: "string"
}
},
Service.attributes
);
get topics() {
if (!this._topics) {
this._topics = new Map();
}
return this._topics;
}
get topicsByName() {
if (!this._topicsByName) {
this._topicsByName = new Map();
}
return this._topicsByName;
}
createTopic(name, options) {
let topic = this.topicsByName.get(name);
if (!topic) {
topic = new Topic(this, name, options);
this.topicsByName.set(name, topic);
this.topics.set(topic.key, topic);
}
return topic;
}
/**
* On demand create topic endpoints.
* @param {string} name
* @param {Object|string} definition
* @return {Class} TopicEndpoint if name starts with 'topic.'
*/
endpointFactoryFromConfig(name, definition, ic) {
if (TopicEndpoint.isTopicName(name)) {
return TopicEndpoint;
}
if (PeersEndpoint.isPeersName(name)) {
return PeersEndpoint;
}
return super.endpointFactoryFromConfig(name, definition, ic);
}
async _start() {
const swarm = (this.swarm = new Hyperswarm({
dht: this.dht,
maxPeers: this.maxPeers
}));
/*
this.discovery = swarm.join(
topic,
? {
server: this.server,
client: this.client
}
);
*/
swarm.on("update", () => {
console.log("Hyperswarm update", swarm);
});
swarm.on("peer", peer => {
const topic = this.topics.get(peer.topic);
topic.addPeer(peer);
});
swarm.on("connection", async (socket, peerInfo) => {
console.log(socket, peerInfo);
/*
this.trace(
`connection: peer=${info.peer ? "true" : "false"} client=${
info.client ? "true" : "false"
} ${JSON.stringify(socket.address())} ${socket.remoteAddress}`
);*/
if (peerInfo) {
const topic = this.topics.get(peerInfo.peer.topic);
this.trace(`Connection for topic ${topic.name}`);
topic.addSocket(socket);
socket.on("drain", () => this.trace("socket drain"));
socket.on("timeout", () => this.trace("socket timeout"));
const encode = new Encode();
pipeline(encode, socket, e => this.trace(`Encoding pipeline end ${e}`));
this.trace(`Encoding pipeline established ${topic.name}`);
}
const decode = new Decode({ objectMode: true, encoding: "utf8" });
pipeline(socket, decode, e => this.trace(`Decoding pipeline end ${e}`));
this.trace(`Decoding pipeline established`);
decode.on("data", data => this.trace(`got ${data}`));
});
swarm.on("disconnection", (socket, info) => {
if (info.peer) {
this.trace(`disconnection: ${JSON.stringify(info.peer)}`);
const topic = this.topics.get(info.peer.topic);
if (topic) {
topic.removePeer(info.peer);
} else {
this.trace(`disconnection: unknown topic`);
}
}
});
await Promise.all(
[...this.topics.values()].map(topic => {
this.trace(`join topic ${topic.name} ${JSON.stringify(topic.options)}`);
return new Promise(resolve => {
this.swarm.join(topic.key, topic.options, () => {
this.trace(`joined topic ${topic.name}`);
resolve();
});
});
})
);
}
async _stop() {
return Promise.all(
[...this.topics.values()].map(topic => {
this.trace(`leave topic ${topic.name}`);
return new Promise(resolve => {
this.swarm.join(topic.key, () => {
this.trace(`leaved topic ${topic.name}`);
resolve();
});
});
})
);
}
}
export default ServiceSwarm;