2023-10-23 22:44:46 +02:00

168 lines
3.7 KiB
JavaScript

/*
to crate form:
-> generate pubkey/private key (maybe derived from wallet)
-> create scheme
-> sign scheme and send
creating form {
id(pubkey),
scheme: { nonce, scheme },
signature: "",
}
updating form {
id(pubkey),
nonce: 0,
scheme: { nonce, scheme },
signature: "",
}
fetching form:
-> get form by pubkey
-> is signature valid
-> encrypt answers
-> send update
answering form {
id(pubkey),
answers: encrypted({ nonce, answers }),
}
reading answers:
-> is possible to decrypt
->
*/
import {
createDecoder,
createEncoder,
createLightNode,
waitForRemotePeer,
} from "@waku/sdk";
import { enrTree, wakuDnsDiscovery } from "@waku/dns-discovery";
import * as utils from "@waku/utils/bytes";
const VERSION = `0.0.1-rc-1`;
export class Waku {
constructor(node) {
this.node = node;
}
static async create() {
const node = await createLightNode({
defaultBootstrap: true,
libp2p: {
peerDiscovery: [
wakuDnsDiscovery([enrTree["PROD"]], {
lightPush: 6,
store: 6,
filter: 6,
}),
],
},
});
await waitForRemotePeer(node);
return new Waku(node);
}
}
class Form {
constructor({ pubKey, privateKey, waku }) {
this.waku = waku;
this.history = [];
this.pubKey = pubKey;
this.privateKey = privateKey;
this.decoder = createDecoder(this.contentTopic);
this.contentTopic = `/free-form/${VERSION}/definition/proto`;
this.encoder = createEncoder({ contentTopic: this.contentTopic });
}
// Initiates new form
static async create(waku, { scheme }) {
const form = new Form(id, waku);
await form.createNew({ scheme });
return form;
}
// Fetches history of an existing form
static async fetch(waku, id) {
// TODO: throw on attempt to fetch non existing form
const form = new Form(id, waku);
await form.fetchState(id);
return form;
}
async createNew({ scheme }) {
// TODO: throw on attempt to create existing form
const command = {
type: "CREATE",
nonce: 0,
signature: "",
scheme,
};
const payload = this.toPayload(command);
await this.waku.lightPush.send(this.encoder, { payload });
this.history.push(command);
console.log("DEBUG", this.history);
}
async fetchState() {
let historyPromises = [];
for await (const promises of this.waku.store.queryGenerator([
this.decoder,
])) {
historyPromises = [...historyPromises, ...promises];
}
if (!historyPromises.length) {
console.error("DEBUG", "No form info fetched from store");
}
const commandPackets = await Promise.all(historyPromises);
const commands = commandPackets
.map(({ payload }) => {
try {
// validate scheme of command record
// check signature
const command = this.toCommand(payload);
return command;
} catch (e) {
console.error("DEBUG", "failed to parse update command.");
return;
}
})
.filter((c) => !!c);
this.history = [...this.history, ...commands];
console.log("DEBUG", this.history);
}
async patchState({ scheme }) {
if (!this.history.length) {
throw Error("no history");
}
const command = {
type: "PATCH",
nonce: this.history[this.history.length - 1].nonce + 1,
signature: "",
scheme,
};
const payload = this.toPayload(command);
await this.waku.lightPush.send(this.encoder, { payload });
this.history.push(command);
console.log("DEBUG patch", this.history);
}
toPayload(command) {
return utils.utf8ToBytes(JSON.stringify(command));
}
toCommand(payload) {
return JSON.parse(utils.bytesToUtf8(payload));
}
}