mirror of https://github.com/waku-org/waku-lab.git
238 lines
7.6 KiB
TypeScript
238 lines
7.6 KiB
TypeScript
import React, { useEffect, useState } from "react";
|
|
import { generate } from "server-name-generator";
|
|
import { Message } from "./Message";
|
|
import { EPeersByDiscoveryEvents, LightNode, Tags } from "@waku/interfaces";
|
|
import type { PeerId } from "@libp2p/interface";
|
|
|
|
import { useFilterMessages, useStoreMessages } from "@waku/react";
|
|
import type {
|
|
UseMessagesParams,
|
|
UseMessagesResult,
|
|
UsePeersParams,
|
|
UsePeersResults,
|
|
} from "./types";
|
|
import { OrderedSet } from "./ordered_array";
|
|
import { getPeerIdsForProtocol } from "./utils";
|
|
|
|
export const usePersistentNick = (): [
|
|
string,
|
|
React.Dispatch<React.SetStateAction<string>>
|
|
] => {
|
|
const [nick, setNick] = useState<string>(() => {
|
|
const persistedNick = window.localStorage.getItem("nick");
|
|
return persistedNick !== null ? persistedNick : generate();
|
|
});
|
|
useEffect(() => {
|
|
localStorage.setItem("nick", nick);
|
|
}, [nick]);
|
|
|
|
return [nick, setNick];
|
|
};
|
|
|
|
export const useMessages = (params: UseMessagesParams): UseMessagesResult => {
|
|
const { messages: newMessages } = useFilterMessages(params);
|
|
const { messages: storedMessages } = useStoreMessages(params);
|
|
const [localMessages, setLocalMessages] = useState<Message[]>([]);
|
|
|
|
const pushMessages = (msgs: Message[]) => {
|
|
if (!msgs || !msgs.length) {
|
|
return;
|
|
}
|
|
setLocalMessages((prev) => [...prev, ...msgs]);
|
|
};
|
|
|
|
const allMessages = React.useMemo((): OrderedSet<Message> => {
|
|
const allMessages = new OrderedSet(Message.cmp, Message.isEqual);
|
|
|
|
const tomorrow = new Date();
|
|
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
|
|
const _msgs = [...storedMessages, ...newMessages]
|
|
.map(Message.fromWakuMessage)
|
|
.filter((v): v is Message => !!v)
|
|
.filter((v) => v.payloadAsUtf8 !== "")
|
|
// Filter out messages that are "sent" tomorrow are they are likely to be flukes
|
|
.filter((m) => m.timestamp.valueOf() < tomorrow.valueOf());
|
|
allMessages.push(..._msgs);
|
|
allMessages.push(...localMessages);
|
|
|
|
return allMessages;
|
|
}, [storedMessages, newMessages, localMessages]);
|
|
|
|
return [allMessages, pushMessages];
|
|
};
|
|
|
|
// can be safely ignored
|
|
// this is for experiments on waku side around new discovery options
|
|
export const useNodePeers = (node: undefined | LightNode) => {
|
|
const [discoveredBootstrapPeers, setBootstrapPeers] = useState<Set<PeerId>>(
|
|
new Set()
|
|
);
|
|
const [connectedBootstrapPeers, setConnectedBootstrapPeers] = useState<
|
|
Set<PeerId>
|
|
>(new Set());
|
|
const [discoveredPeerExchangePeers, setPeerExchangePeers] = useState<
|
|
Set<PeerId>
|
|
>(new Set());
|
|
const [connectedPeerExchangePeers, setConnectedPeerExchangePeers] = useState<
|
|
Set<PeerId>
|
|
>(new Set());
|
|
|
|
useEffect(() => {
|
|
if (!node) return;
|
|
|
|
const handleDiscoveryBootstrap = async (event: CustomEvent<PeerId>) => {
|
|
(async () => {
|
|
if (!(await isPeerDialable(event.detail, node))) return;
|
|
|
|
setBootstrapPeers((peers) => new Set([...peers, event.detail]));
|
|
})().catch((error) => console.error(error));
|
|
};
|
|
|
|
const handleConnectBootstrap = (event: CustomEvent<PeerId>) => {
|
|
setConnectedBootstrapPeers((peers) => new Set([...peers, event.detail]));
|
|
};
|
|
|
|
const handleDiscoveryPeerExchange = (event: CustomEvent<PeerId>) => {
|
|
(async () => {
|
|
if (!(await isPeerDialable(event.detail, node))) return;
|
|
|
|
setPeerExchangePeers((peers) => new Set([...peers, event.detail]));
|
|
})().catch((error) => console.error(error));
|
|
};
|
|
|
|
const handleConnectPeerExchange = (event: CustomEvent<PeerId>) => {
|
|
setConnectedPeerExchangePeers(
|
|
(peers) => new Set([...peers, event.detail])
|
|
);
|
|
};
|
|
|
|
const initHookData = async () => {
|
|
const { CONNECTED, DISCOVERED } =
|
|
await node.connectionManager.getPeersByDiscovery();
|
|
|
|
setConnectedBootstrapPeers(
|
|
new Set(CONNECTED[Tags.BOOTSTRAP].map((p) => p.id))
|
|
);
|
|
setConnectedPeerExchangePeers(
|
|
new Set(CONNECTED[Tags.PEER_EXCHANGE].map((p) => p.id))
|
|
);
|
|
setBootstrapPeers(new Set(DISCOVERED[Tags.BOOTSTRAP].map((p) => p.id)));
|
|
setPeerExchangePeers(
|
|
new Set(DISCOVERED[Tags.PEER_EXCHANGE].map((p) => p.id))
|
|
);
|
|
|
|
node.libp2p.addEventListener("peer:disconnect", (evt) => {
|
|
const peerId = evt.detail;
|
|
setConnectedBootstrapPeers((peers) => {
|
|
peers.delete(peerId);
|
|
return peers;
|
|
});
|
|
});
|
|
node.connectionManager.addEventListener(
|
|
EPeersByDiscoveryEvents.PEER_DISCOVERY_BOOTSTRAP,
|
|
handleDiscoveryBootstrap
|
|
);
|
|
node.connectionManager.addEventListener(
|
|
EPeersByDiscoveryEvents.PEER_CONNECT_BOOTSTRAP,
|
|
handleConnectBootstrap
|
|
);
|
|
node.connectionManager.addEventListener(
|
|
EPeersByDiscoveryEvents.PEER_DISCOVERY_PEER_EXCHANGE,
|
|
handleDiscoveryPeerExchange
|
|
);
|
|
node.connectionManager.addEventListener(
|
|
EPeersByDiscoveryEvents.PEER_CONNECT_PEER_EXCHANGE,
|
|
handleConnectPeerExchange
|
|
);
|
|
};
|
|
|
|
initHookData();
|
|
|
|
return () => {
|
|
node.connectionManager.removeEventListener(
|
|
EPeersByDiscoveryEvents.PEER_DISCOVERY_BOOTSTRAP,
|
|
handleDiscoveryBootstrap
|
|
);
|
|
node.connectionManager.removeEventListener(
|
|
EPeersByDiscoveryEvents.PEER_CONNECT_BOOTSTRAP,
|
|
handleConnectBootstrap
|
|
);
|
|
node.connectionManager.removeEventListener(
|
|
EPeersByDiscoveryEvents.PEER_DISCOVERY_PEER_EXCHANGE,
|
|
handleDiscoveryPeerExchange
|
|
);
|
|
node.connectionManager.removeEventListener(
|
|
EPeersByDiscoveryEvents.PEER_CONNECT_PEER_EXCHANGE,
|
|
handleConnectPeerExchange
|
|
);
|
|
};
|
|
}, [node]);
|
|
|
|
return {
|
|
discoveredBootstrapPeers,
|
|
connectedBootstrapPeers,
|
|
discoveredPeerExchangePeers,
|
|
connectedPeerExchangePeers,
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Hook returns map of peers for different protocols.
|
|
* If protocol is not implemented on the node peers are undefined.
|
|
* @example
|
|
* const { storePeers } = usePeers({ node });
|
|
* @param {Waku} params.node - Waku node, if not set then no peers will be returned
|
|
* @returns {Object} map of peers, if some of the protocols is not implemented then undefined
|
|
*/
|
|
export const usePeers = (params: UsePeersParams): UsePeersResults => {
|
|
const { node } = params;
|
|
const [peers, setPeers] = React.useState<UsePeersResults>({});
|
|
|
|
useEffect(() => {
|
|
if (!node) {
|
|
return;
|
|
}
|
|
|
|
const listener = async () => {
|
|
// find all the peers that are connected for diff protocols
|
|
const peerIds = node.libp2p.getPeers();
|
|
const peers = await Promise.all(
|
|
peerIds.map((id) => node.libp2p.peerStore.get(id))
|
|
);
|
|
|
|
setPeers({
|
|
allConnected: peers.map((p) => p.id),
|
|
storePeers: getPeerIdsForProtocol(node.store, peers),
|
|
filterPeers: getPeerIdsForProtocol(node.filter, peers),
|
|
lightPushPeers: getPeerIdsForProtocol(node.lightPush, peers),
|
|
});
|
|
};
|
|
|
|
listener(); // populate peers before event is invoked
|
|
node.libp2p.addEventListener("peer:identify", listener);
|
|
return () => {
|
|
node.libp2p.removeEventListener("peer:identify", listener);
|
|
};
|
|
}, [node, setPeers]);
|
|
|
|
return peers;
|
|
};
|
|
|
|
// we only support websocket connections for now
|
|
const isPeerDialable = async (peerId: PeerId, node: LightNode) => {
|
|
const peer = await node.libp2p.peerStore.get(peerId);
|
|
if (!peer) return false;
|
|
if (peer.addresses.length === 0) return false;
|
|
const connectableMultiaddrs = peer.addresses.filter(({ multiaddr }) =>
|
|
isMultiaddrConnectable(multiaddr.toString())
|
|
);
|
|
if (connectableMultiaddrs.length === 0) return false;
|
|
|
|
return true;
|
|
};
|
|
|
|
const isMultiaddrConnectable = (multiaddr: string) => {
|
|
return multiaddr.includes("wss") || multiaddr.includes("/tls/ws");
|
|
};
|