feat: add fallback sync
This commit is contained in:
@ -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) {
|
||||||
|
@ -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>}
|
||||||
|
@ -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) {
|
||||||
|
@ -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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
Reference in New Issue
Block a user