2023-09-01 12:33:32 +00:00
|
|
|
import { concatBytes, ensureBytes } from "@lumeweb/libkernel";
|
2023-09-01 10:55:37 +00:00
|
|
|
import {
|
|
|
|
addHandler,
|
|
|
|
defer,
|
|
|
|
getKey,
|
|
|
|
handlePresentKey as handlePresentKeyModule,
|
|
|
|
} from "@lumeweb/libkernel/module";
|
|
|
|
import type { ActiveQuery } from "@lumeweb/libkernel/module";
|
2023-09-01 12:33:32 +00:00
|
|
|
import {
|
|
|
|
createClient as createSwarmClient,
|
|
|
|
SwarmClient,
|
|
|
|
} from "@lumeweb/kernel-swarm-client";
|
2023-09-01 10:55:37 +00:00
|
|
|
import Protomux from "@lumeweb/kernel-protomux-client";
|
|
|
|
import {
|
2023-11-17 13:57:15 +00:00
|
|
|
CID,
|
2023-09-02 14:57:59 +00:00
|
|
|
CID_HASH_TYPES,
|
2023-09-01 10:55:37 +00:00
|
|
|
createKeyPair,
|
|
|
|
createNode,
|
|
|
|
NodeId,
|
|
|
|
S5NodeConfig,
|
|
|
|
} from "@lumeweb/libs5";
|
2023-09-01 12:33:32 +00:00
|
|
|
import type { S5Node } from "@lumeweb/libs5";
|
|
|
|
import KeyPairEd25519 from "@lumeweb/libs5/lib/ed25519.js";
|
2023-09-01 10:55:37 +00:00
|
|
|
import { Level } from "level";
|
|
|
|
import HyperTransportPeer from "@lumeweb/libs5-transport-hyper";
|
|
|
|
|
|
|
|
const PROTOCOL = "lumeweb.service.s5";
|
|
|
|
|
|
|
|
const moduleReadyDefer = defer();
|
|
|
|
|
2023-09-01 12:33:32 +00:00
|
|
|
let swarm: SwarmClient;
|
|
|
|
let node: S5Node;
|
2023-09-01 10:55:37 +00:00
|
|
|
|
|
|
|
addHandler("presentKey", handlePresentKey);
|
|
|
|
addHandler("ready", ready);
|
2023-09-01 12:33:32 +00:00
|
|
|
addHandler("getRegistryEntry", handleGetRegistryEntry);
|
|
|
|
addHandler("setRegistryEntry", handleSetRegistryEntry);
|
|
|
|
addHandler("registrySubscription", handleRegistrySubscription, {
|
|
|
|
receiveUpdates: true,
|
|
|
|
});
|
2023-11-17 13:57:15 +00:00
|
|
|
addHandler("cat", handleCat);
|
2023-11-17 14:19:33 +00:00
|
|
|
addHandler("stat", handleStat);
|
2023-09-01 10:55:37 +00:00
|
|
|
|
|
|
|
async function handlePresentKey(aq: ActiveQuery) {
|
|
|
|
handlePresentKeyModule({
|
|
|
|
callerInput: {
|
|
|
|
key: aq.callerInput.rootKey,
|
|
|
|
},
|
|
|
|
} as ActiveQuery);
|
|
|
|
|
|
|
|
await setup();
|
|
|
|
|
|
|
|
moduleReadyDefer.resolve();
|
|
|
|
}
|
|
|
|
|
|
|
|
async function setup() {
|
|
|
|
swarm = createSwarmClient();
|
|
|
|
|
2023-09-02 16:44:04 +00:00
|
|
|
const peerConnectedDefer = defer();
|
|
|
|
|
2023-09-01 10:55:37 +00:00
|
|
|
const db = new Level<string, Uint8Array>("s5");
|
|
|
|
await db.open();
|
|
|
|
let config = {
|
|
|
|
keyPair: createKeyPair(await getKey()),
|
|
|
|
db,
|
|
|
|
p2p: {
|
|
|
|
peers: {
|
|
|
|
initial: [],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
} as S5NodeConfig;
|
|
|
|
|
|
|
|
swarm.join(PROTOCOL);
|
|
|
|
await swarm.start();
|
|
|
|
await swarm.ready();
|
|
|
|
|
2023-09-01 12:33:32 +00:00
|
|
|
node = createNode(config);
|
2023-09-01 10:55:37 +00:00
|
|
|
|
|
|
|
await node.start();
|
|
|
|
|
|
|
|
swarm.on("connection", async (peer: any) => {
|
|
|
|
const muxer = Protomux.from(peer);
|
|
|
|
const s5peer = new HyperTransportPeer({
|
|
|
|
muxer,
|
|
|
|
peer,
|
|
|
|
protocol: PROTOCOL,
|
|
|
|
});
|
|
|
|
|
|
|
|
s5peer.id = new NodeId(
|
2023-09-02 14:57:59 +00:00
|
|
|
concatBytes(
|
|
|
|
Uint8Array.from([CID_HASH_TYPES.ED25519]),
|
|
|
|
peer.remotePublicKey,
|
|
|
|
),
|
2023-09-01 10:55:37 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
await s5peer.init();
|
|
|
|
node.services.p2p.onNewPeer(s5peer, true);
|
2023-09-04 00:49:44 +00:00
|
|
|
node.services.p2p.once("peerConnected", peerConnectedDefer.resolve);
|
2023-09-01 10:55:37 +00:00
|
|
|
});
|
2023-09-02 16:44:04 +00:00
|
|
|
|
|
|
|
return peerConnectedDefer.promise;
|
2023-09-01 10:55:37 +00:00
|
|
|
}
|
2023-09-01 12:33:32 +00:00
|
|
|
|
2023-09-01 11:18:23 +00:00
|
|
|
async function ready(aq: ActiveQuery) {
|
2023-09-01 10:55:37 +00:00
|
|
|
await moduleReadyDefer.promise;
|
2023-09-01 11:18:23 +00:00
|
|
|
|
|
|
|
aq.respond();
|
2023-09-01 10:55:37 +00:00
|
|
|
}
|
2023-09-01 12:33:32 +00:00
|
|
|
|
|
|
|
async function handleGetRegistryEntry(aq: ActiveQuery) {
|
|
|
|
if (!("pubkey" in aq.callerInput)) {
|
|
|
|
aq.reject("pubkey required");
|
|
|
|
}
|
|
|
|
|
|
|
|
await moduleReadyDefer.promise;
|
|
|
|
|
|
|
|
let { pubkey } = aq.callerInput;
|
|
|
|
|
2023-09-20 01:09:41 +00:00
|
|
|
pubkey = ensureBytes("registry entry", pubkey, 33);
|
2023-09-03 04:56:34 +00:00
|
|
|
|
2023-09-01 12:33:32 +00:00
|
|
|
const ret = await node.services.registry.get(pubkey);
|
|
|
|
|
|
|
|
if (!ret) {
|
|
|
|
aq.reject("could not find registry entry");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
aq.respond(ret);
|
|
|
|
}
|
2023-09-03 04:56:34 +00:00
|
|
|
|
2023-09-01 12:33:32 +00:00
|
|
|
async function handleSetRegistryEntry(aq: ActiveQuery) {
|
|
|
|
for (const field of ["key", "data", "revision"]) {
|
|
|
|
if (!(field in aq.callerInput)) {
|
|
|
|
aq.reject(`${field} required`);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
await moduleReadyDefer.promise;
|
|
|
|
|
|
|
|
let { key, data, revision } = aq.callerInput;
|
|
|
|
|
|
|
|
key = ensureBytes("registry entry private key", key, 32);
|
|
|
|
|
|
|
|
const sre = node.services.registry.signRegistryEntry({
|
|
|
|
kp: new KeyPairEd25519(key),
|
|
|
|
data,
|
|
|
|
revision,
|
|
|
|
});
|
|
|
|
|
|
|
|
try {
|
|
|
|
await node.services.registry.set(sre);
|
|
|
|
aq.respond(sre);
|
|
|
|
} catch (e) {
|
|
|
|
aq.reject(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function handleRegistrySubscription(aq: ActiveQuery) {
|
|
|
|
if (!("pubkey" in aq.callerInput)) {
|
|
|
|
aq.reject("pubkey required");
|
|
|
|
}
|
|
|
|
|
|
|
|
await moduleReadyDefer.promise;
|
|
|
|
|
|
|
|
let { pubkey } = aq.callerInput;
|
|
|
|
|
|
|
|
pubkey = ensureBytes("registry entry ", pubkey, 32);
|
|
|
|
|
|
|
|
const wait = defer();
|
|
|
|
|
|
|
|
const done = node.services.registry.listen(pubkey, (sre) => {
|
|
|
|
aq.sendUpdate(sre);
|
|
|
|
});
|
|
|
|
|
|
|
|
aq.setReceiveUpdate?.(() => {
|
|
|
|
done();
|
|
|
|
wait.resolve();
|
|
|
|
});
|
|
|
|
|
|
|
|
await wait.promise;
|
|
|
|
aq.respond();
|
|
|
|
}
|
2023-11-17 13:57:15 +00:00
|
|
|
|
|
|
|
async function handleCat(aq: ActiveQuery) {
|
|
|
|
if (!("cid" in aq.callerInput)) {
|
|
|
|
aq.reject("cid required");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
const ret = await node.downloadBytesByHash(
|
|
|
|
CID.decode(aq.callerInput.cid as string).hash,
|
|
|
|
);
|
|
|
|
|
|
|
|
aq.respond(ret);
|
|
|
|
} catch (e) {
|
|
|
|
aq.reject(e);
|
|
|
|
}
|
|
|
|
}
|
2023-11-17 14:19:33 +00:00
|
|
|
async function handleStat(aq: ActiveQuery) {
|
|
|
|
if (!("cid" in aq.callerInput)) {
|
|
|
|
aq.reject("cid required");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
const ret = await node.getMetadataByCID(aq.callerInput.cid);
|
|
|
|
aq.respond(ret.toJson());
|
|
|
|
} catch (e) {
|
|
|
|
aq.reject(e);
|
|
|
|
}
|
|
|
|
}
|