feat: add fallback sync

This commit is contained in:
2024-01-25 16:02:16 +00:00
parent d7460651c8
commit 9a3207bfa3
6 changed files with 57 additions and 30 deletions

View File

@ -8,10 +8,6 @@ export default function NostrLink({ link, depth }: { link: string; depth?: numbe
const nav = tryParseNostrLink(link); const nav = tryParseNostrLink(link);
if (nav?.type === NostrPrefix.PublicKey || nav?.type === NostrPrefix.Profile) { if (nav?.type === NostrPrefix.PublicKey || nav?.type === NostrPrefix.Profile) {
if (nav.id.startsWith("npub")) {
// eslint-disable-next-line no-debugger
debugger;
}
return <Mention link={nav} />; return <Mention link={nav} />;
} else if (nav?.type === NostrPrefix.Note || nav?.type === NostrPrefix.Event || nav?.type === NostrPrefix.Address) { } else if (nav?.type === NostrPrefix.Note || nav?.type === NostrPrefix.Event || nav?.type === NostrPrefix.Address) {
if ((depth ?? 0) > 0) { if ((depth ?? 0) > 0) {

View File

@ -104,7 +104,7 @@ export function SignIn() {
id: "X7xU8J", id: "X7xU8J",
})} })}
value={key} value={key}
onChange={onChange} // TODO should log in directly if nsec or npub is pasted onChange={onChange}
className="new-username" className="new-username"
/> />
{error && <b className="error">{error}</b>} {error && <b className="error">{error}</b>}

View File

@ -31,7 +31,15 @@ interface ConnectionEvents {
unknownMessage: (obj: Array<any>) => void; unknownMessage: (obj: Array<any>) => void;
} }
/**
* SYNC command is an internal command that requests the connection to devise a strategy
* to synchronize based on a set of existing cached events and a filter set.
*/
export type SyncCommand = ["SYNC", id: string, fromSet: Array<TaggedNostrEvent>, ...filters: Array<ReqFilter>]; export type SyncCommand = ["SYNC", id: string, fromSet: Array<TaggedNostrEvent>, ...filters: Array<ReqFilter>];
/**
* Pending REQ queue
*/
interface ConnectionQueueItem { interface ConnectionQueueItem {
obj: ReqCommand | SyncCommand; obj: ReqCommand | SyncCommand;
cb: () => void; cb: () => void;
@ -228,7 +236,6 @@ export class Connection extends EventEmitter<ConnectionEvents> {
break; break;
} }
default: { default: {
this.#log(`Unknown tag: ${tag}`);
this.emit("unknownMessage", msg); this.emit("unknownMessage", msg);
break; break;
} }
@ -333,7 +340,10 @@ export class Connection extends EventEmitter<ConnectionEvents> {
this.#log("Queuing: %O", cmd); this.#log("Queuing: %O", cmd);
} else { } else {
this.ActiveRequests.add(cmd[1]); this.ActiveRequests.add(cmd[1]);
this.#sendRequestCommand(cmd); this.#sendRequestCommand({
obj: cmd,
cb: cbSent,
});
cbSent(); cbSent();
} }
this.emit("change"); this.emit("change");
@ -354,30 +364,42 @@ export class Connection extends EventEmitter<ConnectionEvents> {
for (let x = 0; x < canSend; x++) { for (let x = 0; x < canSend; x++) {
const p = this.PendingRequests.shift(); const p = this.PendingRequests.shift();
if (p) { if (p) {
this.#sendRequestCommand(p.obj); this.#sendRequestCommand(p);
p.cb();
this.#log("Sent pending REQ %O", p.obj); this.#log("Sent pending REQ %O", p.obj);
} }
} }
} }
} }
#sendRequestCommand(cmd: ReqCommand | SyncCommand) { #sendRequestCommand(item: ConnectionQueueItem) {
try { try {
const cmd = item.obj;
if (cmd[0] === "REQ") { if (cmd[0] === "REQ") {
this.ActiveRequests.add(cmd[1]); this.ActiveRequests.add(cmd[1]);
this.send(cmd); this.send(cmd);
} else if (cmd[0] === "SYNC") { } else if (cmd[0] === "SYNC") {
const [_, id, eventSet, ...filters] = cmd;
const lastResortSync = () => {
const latest = eventSet.reduce((acc, v) => (acc = v.created_at > acc ? v.created_at : acc), 0);
const newFilters = filters.map(a => ({
...a,
since: latest,
}));
this.queueReq(["REQ", id, ...newFilters], item.cb);
};
if (this.Info?.software?.includes("strfry")) { if (this.Info?.software?.includes("strfry")) {
const neg = new NegentropyFlow(cmd[1], this, cmd[2], cmd.slice(3) as Array<ReqFilter>); const neg = new NegentropyFlow(id, this, eventSet, filters);
neg.once("finish", filters => { neg.once("finish", filters => {
if (filters.length > 0) { if (filters.length > 0) {
this.queueReq(["REQ", cmd[1], ...filters], () => {}); this.queueReq(["REQ", cmd[1], ...filters], item.cb);
} }
}); });
neg.once("error", () => {
lastResortSync();
});
neg.start(); neg.start();
} else { } else {
throw new Error("SYNC not supported"); lastResortSync();
} }
} }
} catch (e) { } catch (e) {

View File

@ -11,6 +11,11 @@ export interface NegentropyFlowEvents {
* When sync is finished emit a set of filters which can resolve sync * When sync is finished emit a set of filters which can resolve sync
*/ */
finish: (req: Array<ReqFilter>) => void; finish: (req: Array<ReqFilter>) => void;
/**
* If an error is detected and Negentropy flow is not supported
*/
error: () => void;
} }
/** /**
@ -31,8 +36,8 @@ export class NegentropyFlow extends EventEmitter<NegentropyFlowEvents> {
this.#connection = conn; this.#connection = conn;
this.#filters = filters; this.#filters = filters;
this.#connection.on("unknownMessage", this.#handleMessage.bind(this)); this.#connection.on("unknownMessage", msg => this.#handleMessage(msg));
this.#connection.on("notice", n => this.#handleMessage.bind(this)); this.#connection.on("notice", n => this.#handleMessage(["NOTICE", n]));
const storage = new NegentropyStorageVector(); const storage = new NegentropyStorageVector();
set.forEach(a => storage.insert(a.created_at, a.id)); set.forEach(a => storage.insert(a.created_at, a.id));
@ -52,22 +57,25 @@ export class NegentropyFlow extends EventEmitter<NegentropyFlowEvents> {
try { try {
switch (msg[0] as string) { switch (msg[0] as string) {
case "NOTICE": { case "NOTICE": {
if ((msg[1] as string).includes("negentropy disabled")) { const [_, errorMsg] = msg as [string, string];
this.#log("SYNC ERROR: %s", msg[1]); if (errorMsg.includes("negentropy disabled") || errorMsg.includes("negentropy error")) {
this.#cleanup(); this.#log("SYNC ERROR: %s", errorMsg);
this.#cleanup(true);
} }
break; break;
} }
case "NEG-ERROR": { case "NEG-ERROR": {
if (msg[1] !== this.#id) break; const [_, id, errorMsg] = msg as [string, string, string];
this.#log("SYNC ERROR %s", msg[2]); if (id !== this.#id) break;
this.#cleanup(); this.#log("SYNC ERROR %s", errorMsg);
this.#cleanup(true);
break; break;
} }
case "NEG-MSG": { case "NEG-MSG": {
if (msg[1] !== this.#id) break; const [, id, payload] = msg as [string, string, string];
const query = hexToBytes(msg[2] as string); if (id !== this.#id) break;
const [nextMsg, _, need] = this.#negentropy.reconcile(query); const query = hexToBytes(payload);
const [nextMsg, , need] = this.#negentropy.reconcile(query);
if (need.length > 0) { if (need.length > 0) {
this.#need.push(...need.map(bytesToHex)); this.#need.push(...need.map(bytesToHex));
} }
@ -81,14 +89,16 @@ export class NegentropyFlow extends EventEmitter<NegentropyFlowEvents> {
} }
} }
} catch (e) { } catch (e) {
debugger;
console.error(e); console.error(e);
} }
} }
#cleanup() { #cleanup(error = false) {
this.#connection.off("unknownMessage", this.#handleMessage.bind(this)); this.#connection.off("unknownMessage", msg => this.#handleMessage(msg));
this.#connection.off("notice", n => this.#handleMessage.bind(this)); this.#connection.off("notice", n => this.#handleMessage(["NOTICE", n]));
this.emit("finish", this.#need.length > 0 ? [{ ids: this.#need }] : []); this.emit("finish", this.#need.length > 0 ? [{ ids: this.#need }] : []);
if (error) {
this.emit("error");
}
} }
} }

View File

@ -39,7 +39,6 @@ export class NegentropyStorageVector {
for (let i = 1; i < this.#items.length; i++) { for (let i = 1; i < this.#items.length; i++) {
if (itemCompare(this.#items[i - 1], this.#items[i]) === 0) { if (itemCompare(this.#items[i - 1], this.#items[i]) === 0) {
debugger;
throw Error("duplicate item inserted"); throw Error("duplicate item inserted");
} }
} }

View File

@ -147,7 +147,7 @@ export class RequestBuilder {
const ts = unixNowMs() - start; const ts = unixNowMs() - start;
this.#log("buildDiff %s %d ms +%d", this.id, ts, diff.length); this.#log("buildDiff %s %d ms +%d", this.id, ts, diff.length);
if (diff.length > 0) { if (diff.length > 0) {
// todo: fix // todo: fix for explicit relays
return splitFlatByWriteRelays(system.relayCache, diff).map(a => { return splitFlatByWriteRelays(system.relayCache, diff).map(a => {
return { return {
strategy: RequestStrategy.AuthorsRelays, strategy: RequestStrategy.AuthorsRelays,