kernel-swarm/src/index.ts

291 lines
5.9 KiB
TypeScript
Raw Normal View History

2022-07-21 17:03:18 +00:00
// @ts-ignore
2023-02-01 12:50:45 +00:00
import Hyperswarm from "@lumeweb/hyperswarm-web";
2022-07-21 17:03:18 +00:00
import type { ActiveQuery } from "libkmodule";
import { addHandler, getSeed, handleMessage } from "libkmodule";
import { handlePresentSeed as handlePresentSeedModule } from "libkmodule/dist/seed.js";
2022-08-13 19:11:18 +00:00
import type { Buffer } from "buffer";
2023-02-01 12:50:45 +00:00
import * as ed from "@noble/ed25519";
import b4a from "b4a";
2022-07-21 17:03:18 +00:00
interface DhtConnection {
2023-02-01 12:50:45 +00:00
swarm: number;
conn: any;
}
const connections = new Map<number, DhtConnection>();
2023-02-01 12:50:45 +00:00
const swarmInstances = new Map<number, Hyperswarm>();
2022-07-21 17:03:18 +00:00
2023-02-01 12:50:45 +00:00
let defaultSwarm: Hyperswarm;
2022-08-31 19:35:24 +00:00
let moduleReadyResolve: Function;
let moduleReady: Promise<void> = new Promise((resolve) => {
moduleReadyResolve = resolve;
});
2022-07-21 17:03:18 +00:00
onmessage = handleMessage;
2023-02-01 12:50:45 +00:00
function idFactory(start = 1) {
2022-08-31 19:35:24 +00:00
let id = start;
return function nextId() {
const nextId = id;
2023-02-01 12:50:45 +00:00
id += 1;
2022-08-31 19:35:24 +00:00
return nextId;
};
}
2023-02-01 12:50:45 +00:00
const getSwarmId = idFactory();
const getSocketId = idFactory();
2022-07-21 17:03:18 +00:00
addHandler("presentSeed", handlePresentSeed);
2023-02-01 12:50:45 +00:00
addHandler("joinPeer", handleJoinPeer);
addHandler("getPeerByPubkey", handleGetPeerByPubkey);
2022-07-21 17:03:18 +00:00
addHandler("listenSocketEvent", handleListenSocketEvent, {
receiveUpdates: true,
});
2022-08-13 19:12:49 +00:00
addHandler("socketExists", handleSocketExists);
2022-07-21 17:03:18 +00:00
addHandler("close", handleCloseSocketEvent);
2023-02-01 12:50:45 +00:00
addHandler("socketWrite", handleWriteSocketEvent);
2022-07-21 17:03:18 +00:00
addHandler("addRelay", handleAddRelay);
addHandler("removeRelay", handleRemoveRelay);
addHandler("clearRelays", handleClearRelays);
addHandler("getRelays", handleGetRelays);
2022-07-21 17:03:18 +00:00
addHandler("ready", handleReady);
async function handlePresentSeed(aq: ActiveQuery) {
2023-02-01 12:50:45 +00:00
handlePresentSeedModule({
callerInput: {
seed: {
publicKey: await ed.getPublicKey(aq.callerInput.rootKey),
secretKey: aq.callerInput.rootKey,
},
},
} as ActiveQuery);
if (!defaultSwarm) {
defaultSwarm = swarmInstances.get(await createSwarm()) as Hyperswarm;
}
2022-08-31 19:35:24 +00:00
moduleReadyResolve();
}
2023-02-01 12:50:45 +00:00
async function createSwarm(): Promise<number> {
const swarmInstance = new Hyperswarm({ keyPair: await getSeed() });
const id = getSwarmId();
swarmInstances.set(id, swarmInstance);
2023-02-01 12:50:45 +00:00
swarmInstance.on("connection", (peer) => {
const socketId = getSocketId();
connections.set(socketId, { swarm: id, conn: peer });
2023-02-01 12:50:45 +00:00
peer.on("close", () => {
connections.delete(socketId);
});
2022-07-21 17:03:18 +00:00
});
2023-02-01 12:50:45 +00:00
return id;
2022-07-21 17:03:18 +00:00
}
function handleListenSocketEvent(aq: ActiveQuery) {
const { event = null } = aq.callerInput;
const socket = validateConnection(aq);
if (!socket) {
2022-07-21 17:03:18 +00:00
return;
}
if (!event) {
aq.reject("Invalid event");
return;
}
2022-09-19 12:11:54 +00:00
let responded = false;
const respond = () => {
if (responded) {
return;
}
responded = true;
aq.respond();
};
2022-07-21 17:03:18 +00:00
const cb = (data: Buffer) => {
aq.sendUpdate(data);
};
socket.on(event, cb);
socket.on("close", () => {
2022-09-19 12:11:54 +00:00
socket.off(event, cb);
respond();
2022-07-21 17:03:18 +00:00
});
aq.setReceiveUpdate?.((data: any) => {
switch (data?.action) {
case "off":
2022-09-19 12:11:54 +00:00
socket.off(event, cb);
respond();
2022-07-21 17:03:18 +00:00
break;
}
});
}
2022-08-13 19:12:49 +00:00
async function handleSocketExists(aq: ActiveQuery) {
const { id = null } = aq.callerInput;
2023-02-01 12:50:45 +00:00
aq.respond(connections.has(Number(id)));
2022-08-13 19:12:49 +00:00
}
2022-07-21 17:03:18 +00:00
function handleCloseSocketEvent(aq: ActiveQuery) {
const socket = validateConnection(aq);
2022-07-21 17:03:18 +00:00
if (!socket) {
2022-07-21 17:03:18 +00:00
return;
}
socket.end();
2022-07-21 17:03:18 +00:00
aq.respond();
}
function handleWriteSocketEvent(aq: ActiveQuery) {
const socket = validateConnection(aq);
2022-07-21 17:03:18 +00:00
if (!socket) {
2022-07-21 17:03:18 +00:00
return;
}
const { message = null } = aq.callerInput;
if (!message) {
aq.reject("empty message");
return false;
}
socket.write(message);
2022-07-21 17:03:18 +00:00
aq.respond();
}
function validateConnection(aq: ActiveQuery): any | boolean {
2022-07-21 17:03:18 +00:00
const { id = null } = aq.callerInput;
2023-02-01 12:50:45 +00:00
if (!id || !connections.has(id)) {
2022-07-21 17:03:18 +00:00
aq.reject("Invalid connection id");
return false;
}
2023-02-01 12:50:45 +00:00
return connections.get(id)?.conn;
}
2023-02-01 12:50:45 +00:00
async function getSwarm(aq: ActiveQuery): Promise<Hyperswarm> {
2022-08-31 19:35:24 +00:00
await moduleReady;
2023-02-01 12:50:45 +00:00
let swarm;
2022-08-31 19:35:24 +00:00
if ("callerInput" in aq && aq.callerInput) {
2023-02-01 12:50:45 +00:00
swarm = aq.callerInput.swarm ?? null;
2023-02-01 12:50:45 +00:00
if (swarm && !swarmInstances.has(swarm)) {
const error = "Invalid swarm id";
2022-08-31 19:35:24 +00:00
aq.reject(error);
throw new Error(error);
}
}
2023-02-01 12:50:45 +00:00
if (!swarm) {
return defaultSwarm;
}
2023-02-01 12:50:45 +00:00
return swarmInstances.get(swarm) as Hyperswarm;
2022-07-21 17:03:18 +00:00
}
async function handleAddRelay(aq: ActiveQuery) {
const { pubkey = null } = aq.callerInput;
if (!pubkey) {
aq.reject("invalid pubkey");
return;
}
2023-02-01 12:50:45 +00:00
const dht = await getSwarm(aq);
2022-08-31 19:35:24 +00:00
2022-07-21 17:03:18 +00:00
aq.respond(await dht.addRelay(pubkey));
}
2022-08-31 19:35:24 +00:00
async function handleRemoveRelay(aq: ActiveQuery) {
2022-07-21 17:03:18 +00:00
const { pubkey = null } = aq.callerInput;
if (!pubkey) {
aq.reject("invalid pubkey");
return;
}
2023-02-01 12:50:45 +00:00
const dht = await getSwarm(aq);
2022-08-31 19:35:24 +00:00
2022-07-21 17:03:18 +00:00
aq.respond(dht.removeRelay(pubkey));
}
2022-08-31 19:35:24 +00:00
async function handleClearRelays(aq: ActiveQuery) {
2023-02-01 12:50:45 +00:00
const dht = await getSwarm(aq);
2022-07-21 17:03:18 +00:00
dht.clearRelays();
aq.respond();
}
2022-08-31 19:35:24 +00:00
async function handleGetRelays(aq: ActiveQuery) {
2023-02-01 12:50:45 +00:00
aq.respond(await (await getSwarm(aq)).relays);
}
2023-02-01 12:50:45 +00:00
async function handleJoinPeer(aq: ActiveQuery) {
const { topic = null } = aq.callerInput;
const swarm = await getSwarm(aq);
if (!topic) {
aq.reject("invalid topic");
return;
}
if (!b4a.isBuffer(topic)) {
aq.reject("topic must be a buffer");
return;
}
// @ts-ignore
swarm.join(topic);
2022-07-21 17:03:18 +00:00
aq.respond();
}
2023-02-01 12:50:45 +00:00
async function handleGetPeerByPubkey(aq: ActiveQuery) {
const { pubkey = null } = aq.callerInput;
2023-02-01 12:50:45 +00:00
const swarm = await getSwarm(aq);
2023-02-01 12:50:45 +00:00
if (!pubkey) {
aq.reject("invalid topic");
return;
}
if (!b4a.isBuffer(pubkey)) {
aq.reject("pubkey must be a buffer");
return;
}
2023-02-01 12:50:45 +00:00
// @ts-ignore
if (!swarm._allConnections.has(pubkey)) {
aq.reject("peer does not exist");
return;
}
// @ts-ignore
const peer = swarm._allConnections.get(pubkey);
aq.respond(
[...connections.entries()].filter((conn) => {
return conn[1].conn === peer;
})[0][0]
);
}
2023-02-01 12:50:45 +00:00
async function handleReady(aq: ActiveQuery) {
const swarm = await getSwarm(aq);
// @ts-ignore
await swarm.ready();
aq.respond();
}