diff --git a/package.json b/package.json index 9bd2aea..34ab01b 100644 --- a/package.json +++ b/package.json @@ -9,41 +9,48 @@ "format": "prettier -w src", "build-script": "tsc --project tsconfig.build.json && mv dist-build/build.js dist-build/build.mjs", "compile": "npm run build-script && node build.js", - "build": "rimraf node_modules/@hyperswarm/secret-stream/node_modules node_modules/@lumeweb/dht-web/node_modules && npm run compile && node ./dist-build/build.mjs dev" + "build": "npm run compile && node ./dist-build/build.mjs dev" }, "type": "module", "dependencies": { - "@lumeweb/dht-web": "https://github.com/LumeWeb/dht-web.git", - "hyperswarm": "^4.0.2", - "libkmodule": "^0.2.12", + "@lumeweb/hyperswarm-web": "git+https://git.lumeweb.com/LumeWeb/hyperswarm-web.git", + "@noble/ed25519": "^1.7.1", + "b4a": "^1.6.1", + "hyperswarm": "^4.3.7", + "libkmodule": "^0.2.53", "libskynet": "^0.0.62", - "noise-handshake": "https://github.com/LumeWeb/noise-handshake.git", - "randombytes": "https://github.com/LumeWeb/randombytes-browser.git" + "noise-handshake": "github:LumeWeb/noise-handshake", + "randombytes": "github:LumeWeb/randombytes-browser" }, "devDependencies": { - "@rollup/plugin-commonjs": "^22.0.1", + "@rollup/plugin-commonjs": "^22.0.2", "@rollup/plugin-node-resolve": "^13.3.0", - "@rollup/plugin-typescript": "^8.3.3", + "@rollup/plugin-typescript": "^8.5.0", "@screamingvoid/sodium-universal": "^0.1.1", - "@types/jest": "^28.1.3", + "@scure/bip39": "^1.1.0", + "@skynetlabs/skynet-nodejs": "^2.9.0", + "@types/b4a": "^1.6.0", + "@types/jest": "^28.1.8", "@types/read": "^0.0.29", "buffer": "^6.0.3", + "cli-progress": "^3.11.2", "crypto-browserify": "^3.12.0", - "esbuild": "^0.14.48", + "esbuild": "^0.14.54", "inspectpack": "^4.7.1", - "jest": "^28.1.1", - "jest-puppeteer": "^6.1.0", - "libskynetnode": "^0.1.3", - "prettier": "^2.7.1", + "jest": "^28.1.3", + "jest-puppeteer": "^6.2.0", + "libskynetnode": "^0.1.4", + "prettier": "^2.8.3", "process": "^0.11.10", - "puppeteer": "^15.2.0", + "puppeteer": "^15.5.0", + "random-number-csprng": "^1.0.2", "read": "^1.0.7", - "rollup": "^2.75.7", + "rollup": "^2.79.1", "rollup-plugin-polyfill-node": "^0.9.0", "stream-browserify": "^3.0.0", - "ts-loader": "^9.3.1", - "typescript": "^4.7.4", - "webpack": "^5.73.0", + "ts-loader": "^9.4.2", + "typescript": "^4.9.5", + "webpack": "^5.75.0", "webpack-cli": "^4.10.0" }, "browser": { diff --git a/src/index.ts b/src/index.ts index ed15428..1abbfe1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,20 +1,21 @@ // @ts-ignore -import DHT from "@lumeweb/dht-web"; +import Hyperswarm from "@lumeweb/hyperswarm-web"; import type { ActiveQuery } from "libkmodule"; import { addHandler, getSeed, handleMessage } from "libkmodule"; import { handlePresentSeed as handlePresentSeedModule } from "libkmodule/dist/seed.js"; import type { Buffer } from "buffer"; -import { hexToBuf } from "libskynet"; +import * as ed from "@noble/ed25519"; +import b4a from "b4a"; interface DhtConnection { - dht: number; + swarm: number; conn: any; } const connections = new Map(); -const dhtInstances = new Map(); +const swarmInstances = new Map(); -let defaultDht: DHT; +let defaultSwarm: Hyperswarm; let moduleReadyResolve: Function; let moduleReady: Promise = new Promise((resolve) => { @@ -22,120 +23,67 @@ let moduleReady: Promise = new Promise((resolve) => { }); onmessage = handleMessage; -function idFactory(start = 1, step = 1, limit = 2 ** 32) { +function idFactory(start = 1) { let id = start; return function nextId() { const nextId = id; - id += step; - if (id >= limit) id = start; + id += 1; return nextId; }; } -const nextId = idFactory(1); +const getSwarmId = idFactory(); +const getSocketId = idFactory(); addHandler("presentSeed", handlePresentSeed); -addHandler("openDht", handleOpenDht); -addHandler("closeDht", handleCloseDht); -addHandler("connect", handleConnect); +addHandler("joinPeer", handleJoinPeer); +addHandler("getPeerByPubkey", handleGetPeerByPubkey); addHandler("listenSocketEvent", handleListenSocketEvent, { receiveUpdates: true, }); addHandler("socketExists", handleSocketExists); addHandler("close", handleCloseSocketEvent); -addHandler("write", handleWriteSocketEvent); +addHandler("socketWrite", handleWriteSocketEvent); addHandler("addRelay", handleAddRelay); addHandler("removeRelay", handleRemoveRelay); addHandler("clearRelays", handleClearRelays); addHandler("getRelays", handleGetRelays); -addHandler("getRelayServers", handleGetRelayServers); addHandler("ready", handleReady); async function handlePresentSeed(aq: ActiveQuery) { - const keyPair = aq.callerInput.myskyRootKeypair; - handlePresentSeedModule({ callerInput: { seed: keyPair } } as ActiveQuery); - if (!defaultDht) { - defaultDht = dhtInstances.get(await createDht()) as DHT; + 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; } moduleReadyResolve(); } -async function handleOpenDht(aq: ActiveQuery) { - const id = await createDht(); - aq.respond({ dht: id }); -} +async function createSwarm(): Promise { + const swarmInstance = new Hyperswarm({ keyPair: await getSeed() }); + const id = getSwarmId(); + swarmInstances.set(id, swarmInstance); -async function handleCloseDht(aq: ActiveQuery) { - const { dht = null } = aq.callerInput; + swarmInstance.on("connection", (peer) => { + const socketId = getSocketId(); + connections.set(socketId, { swarm: id, conn: peer }); - if (!dht) { - aq.reject("Invalid DHT id"); - return; - } - - if (dht === defaultDht) { - aq.reject("Cannot close default DHT"); - return; - } - - dhtInstances.delete(dht); - Array.from(connections.values()) - .filter((item) => item.dht === dht) - .forEach((item) => { - item.conn.end(); + peer.on("close", () => { + connections.delete(socketId); }); + }); - aq.respond(); -} - -async function createDht(): Promise { - const dhtInstance = new DHT({ keyPair: await getSeed() }); - const id = nextId(); - dhtInstances.set(id, dhtInstance); return id; } -async function handleConnect(aq: ActiveQuery) { - const { pubkey, options = {} } = aq.callerInput; - - let socket: any; - - const dht = await getDht(aq); - - try { - // @ts-ignore - socket = await dht.connect( - typeof pubkey === "string" ? hexToBuf(pubkey).shift() : pubkey, - options - ); - } catch (e: any) { - aq.reject(e); - return; - } - - const id = nextId(); - - socket.on("open", () => { - let dhtId: any = [...dhtInstances.entries()].filter( - (item) => item[1] === dht - ); - dhtId = dhtId.shift()[0]; - - setDhtConnection(id, dhtId as number, socket); - aq.respond({ id }); - }); - - socket.on("end", () => { - deleteDhtConnection(id); - }); - - socket.on("error", (e: any) => { - deleteDhtConnection(id); - aq.reject(e); - }); -} - function handleListenSocketEvent(aq: ActiveQuery) { const { event = null } = aq.callerInput; @@ -183,7 +131,7 @@ function handleListenSocketEvent(aq: ActiveQuery) { async function handleSocketExists(aq: ActiveQuery) { const { id = null } = aq.callerInput; - aq.respond(hasDhtConnection(Number(id))); + aq.respond(connections.has(Number(id))); } function handleCloseSocketEvent(aq: ActiveQuery) { @@ -219,32 +167,32 @@ function handleWriteSocketEvent(aq: ActiveQuery) { function validateConnection(aq: ActiveQuery): any | boolean { const { id = null } = aq.callerInput; - if (!id || !hasDhtConnection(id)) { + if (!id || !connections.has(id)) { aq.reject("Invalid connection id"); return false; } - return getDhtConnection(id)?.conn; + return connections.get(id)?.conn; } -async function getDht(aq: ActiveQuery): Promise { +async function getSwarm(aq: ActiveQuery): Promise { await moduleReady; - let dht; + let swarm; if ("callerInput" in aq && aq.callerInput) { - dht = aq.callerInput.dht ?? null; + swarm = aq.callerInput.swarm ?? null; - if (dht && !dhtInstances.has(dht)) { - const error = "Invalid DHT id"; + if (swarm && !swarmInstances.has(swarm)) { + const error = "Invalid swarm id"; aq.reject(error); throw new Error(error); } } - if (!dht) { - return defaultDht; + if (!swarm) { + return defaultSwarm; } - return dhtInstances.get(dht) as DHT; + return swarmInstances.get(swarm) as Hyperswarm; } async function handleAddRelay(aq: ActiveQuery) { @@ -255,7 +203,7 @@ async function handleAddRelay(aq: ActiveQuery) { return; } - const dht = await getDht(aq); + const dht = await getSwarm(aq); aq.respond(await dht.addRelay(pubkey)); } @@ -268,13 +216,13 @@ async function handleRemoveRelay(aq: ActiveQuery) { return; } - const dht = await getDht(aq); + const dht = await getSwarm(aq); aq.respond(dht.removeRelay(pubkey)); } async function handleClearRelays(aq: ActiveQuery) { - const dht = await getDht(aq); + const dht = await getSwarm(aq); dht.clearRelays(); @@ -282,29 +230,61 @@ async function handleClearRelays(aq: ActiveQuery) { } async function handleGetRelays(aq: ActiveQuery) { - aq.respond(await (await getDht(aq)).relays); + aq.respond(await (await getSwarm(aq)).relays); } -async function handleGetRelayServers(aq: ActiveQuery) { - aq.respond(await (await getDht(aq)).relayServers); + +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); + aq.respond(); +} +async function handleGetPeerByPubkey(aq: ActiveQuery) { + const { pubkey = null } = aq.callerInput; + + const swarm = await getSwarm(aq); + + if (!pubkey) { + aq.reject("invalid topic"); + return; + } + + if (!b4a.isBuffer(pubkey)) { + aq.reject("pubkey must be a buffer"); + return; + } + + // @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] + ); } async function handleReady(aq: ActiveQuery) { - await (await getDht(aq)).ready(); + const swarm = await getSwarm(aq); + // @ts-ignore + await swarm.ready(); aq.respond(); } - -function setDhtConnection(id: number, dht: number, conn: any) { - connections.set(id, { dht, conn }); -} - -function getDhtConnection(id: number) { - return connections.get(id); -} - -function hasDhtConnection(id: number) { - return connections.has(id); -} - -function deleteDhtConnection(id: number) { - connections.delete(id); -}