feat!: add new hooks and improvements (#3)
* use React global var * use undefined instead on null * use react as peer dependency * make ephemeral default to false * set default ephemeral to false, use node instead of waku naming * implement useStoreMessages * fix types * export useStoreMessages * make content pair set initially * remove deps for useEffect for createWaku to prevent re rendering * prevent setting of empty messages * accept undefined node, handle empty message case * accept undefined node, handle empty message case * add TODOs * rename to useCreateContentPair * remove export of WakuContext, create ContetnPair provider * remove export of WakuContext, create ContetnPair provider * fix lint * fix typo * remove export * fix prettier * make decoded optional * add jsdocs * add useLightPush hook * update types, add usePeers, add prettierignore * remove full node hook, provider * remove export * remove FullNode stuff
This commit is contained in:
parent
bd3bfb8134
commit
e4d0106499
|
@ -29,7 +29,7 @@
|
||||||
],
|
],
|
||||||
"cypress/no-unnecessary-waiting": "off",
|
"cypress/no-unnecessary-waiting": "off",
|
||||||
"react-hooks/rules-of-hooks": "error",
|
"react-hooks/rules-of-hooks": "error",
|
||||||
"react-hooks/exhaustive-deps": "error",
|
"react-hooks/exhaustive-deps": "warn",
|
||||||
"react/display-name": "warn",
|
"react/display-name": "warn",
|
||||||
"react/prop-types": "off",
|
"react/prop-types": "off",
|
||||||
"no-console": ["error"],
|
"no-console": ["error"],
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
dist
|
File diff suppressed because it is too large
Load Diff
|
@ -60,8 +60,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@waku/core": "^0.0.10",
|
"@waku/core": "^0.0.10",
|
||||||
"@waku/create": "^0.0.6",
|
"@waku/create": "^0.0.6"
|
||||||
"react": "^18.2.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-commonjs": "^24.0.1",
|
"@rollup/plugin-commonjs": "^24.0.1",
|
||||||
|
@ -91,6 +90,9 @@
|
||||||
"rollup-plugin-typescript2": "^0.34.1",
|
"rollup-plugin-typescript2": "^0.34.1",
|
||||||
"typescript": "^4.9.5"
|
"typescript": "^4.9.5"
|
||||||
},
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17 || ^18"
|
||||||
|
},
|
||||||
"bundlewatch": {
|
"bundlewatch": {
|
||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import type { ContentPair, ReactChildrenProps } from "./types";
|
||||||
|
import { useCreateContentPair } from "./useCreatContentPair";
|
||||||
|
|
||||||
|
type ContentPairContextType = Partial<ContentPair>;
|
||||||
|
|
||||||
|
const ContentPairContext = React.createContext<ContentPairContextType>({
|
||||||
|
decoder: undefined,
|
||||||
|
encoder: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to retrieve Encoder/Decoder pair from Context.
|
||||||
|
* @example
|
||||||
|
* const { encoder, decoder } = useContentPair();
|
||||||
|
* @returns {Object} { encoder, decoder }
|
||||||
|
*/
|
||||||
|
export const useContentPair = (): ContentPairContextType =>
|
||||||
|
React.useContext(ContentPairContext);
|
||||||
|
|
||||||
|
type ContentPairProviderProps = ReactChildrenProps & {
|
||||||
|
contentTopic: string;
|
||||||
|
ephemeral?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider for creating Encoder/Decoder pair based on contentTopic
|
||||||
|
* @example
|
||||||
|
* const App = (props) => (
|
||||||
|
* <ContentPairProvider contentTopic="/toy-chat/2/huilong/proto">
|
||||||
|
* <Component />
|
||||||
|
* </ContentPairProvider>
|
||||||
|
* );
|
||||||
|
* const Component = (props) => {
|
||||||
|
* const { encoder, decoder } = useContentPair();
|
||||||
|
* ...
|
||||||
|
* };
|
||||||
|
* @param {string} contentTopic - content topic for configuring the pair
|
||||||
|
* @param {boolean} ephemeral - flag to set messages ephemeral according to RFC https://rfc.vac.dev/spec/14/
|
||||||
|
* @returns React ContentPair Provider component
|
||||||
|
*/
|
||||||
|
export const ContentPairProvider: React.FunctionComponent<
|
||||||
|
ContentPairProviderProps
|
||||||
|
> = (props) => {
|
||||||
|
const result = useCreateContentPair(props.contentTopic, props.ephemeral);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContentPairContext.Provider value={result}>
|
||||||
|
{props.children}
|
||||||
|
</ContentPairContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
|
@ -3,23 +3,19 @@ import type { Waku } from "@waku/interfaces";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
BootstrapNodeOptions,
|
BootstrapNodeOptions,
|
||||||
CrateWakuHook,
|
CrateNodeResult,
|
||||||
FullNodeOptions,
|
|
||||||
LightNodeOptions,
|
LightNodeOptions,
|
||||||
|
ReactChildrenProps,
|
||||||
RelayNodeOptions,
|
RelayNodeOptions,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import {
|
import { useCreateLightNode, useCreateRelayNode } from "./useCreateWaku";
|
||||||
useCreateFullNode,
|
|
||||||
useCreateLightNode,
|
|
||||||
useCreateRelayNode,
|
|
||||||
} from "./useCreateWaku";
|
|
||||||
|
|
||||||
type WakuContextType<T extends Waku> = CrateWakuHook<T>;
|
type WakuContextType<T extends Waku> = CrateNodeResult<T>;
|
||||||
|
|
||||||
export const WakuContext = React.createContext<WakuContextType<Waku>>({
|
const WakuContext = React.createContext<WakuContextType<Waku>>({
|
||||||
node: null,
|
node: undefined,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
error: null,
|
error: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -37,10 +33,6 @@ export const WakuContext = React.createContext<WakuContextType<Waku>>({
|
||||||
export const useWaku = <T extends Waku>(): WakuContextType<T> =>
|
export const useWaku = <T extends Waku>(): WakuContextType<T> =>
|
||||||
React.useContext(WakuContext) as WakuContextType<T>;
|
React.useContext(WakuContext) as WakuContextType<T>;
|
||||||
|
|
||||||
type ReactChildrenProps = {
|
|
||||||
children?: React.ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ProviderProps<T> = ReactChildrenProps & BootstrapNodeOptions<T>;
|
type ProviderProps<T> = ReactChildrenProps & BootstrapNodeOptions<T>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -102,33 +94,3 @@ export const RelayNodeProvider: React.FunctionComponent<
|
||||||
<WakuContext.Provider value={result}>{props.children}</WakuContext.Provider>
|
<WakuContext.Provider value={result}>{props.children}</WakuContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Provider for creating Full Node based on options passed.
|
|
||||||
* @example
|
|
||||||
* const App = (props) => (
|
|
||||||
* <FullNodeProvider options={{...}}>
|
|
||||||
* <Component />
|
|
||||||
* </FullNodeProvider>
|
|
||||||
* );
|
|
||||||
* const Component = (props) => {
|
|
||||||
* const { node, isLoading, error } = useWaku<FullNode>();
|
|
||||||
* ...
|
|
||||||
* };
|
|
||||||
* @param {Object} props - options to create a node and other React props
|
|
||||||
* @param {FullNodeOptions} props.options - optional options for creating Full Node
|
|
||||||
* @param {Protocols} props.protocols - optional protocols list to initiate node with
|
|
||||||
* @returns React Full Node provider component
|
|
||||||
*/
|
|
||||||
export const FullNodeProvider: React.FunctionComponent<
|
|
||||||
ProviderProps<FullNodeOptions>
|
|
||||||
> = (props) => {
|
|
||||||
const result = useCreateFullNode({
|
|
||||||
options: props.options,
|
|
||||||
protocols: props.protocols,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<WakuContext.Provider value={result}>{props.children}</WakuContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
24
src/index.ts
24
src/index.ts
|
@ -1,15 +1,9 @@
|
||||||
export { FullNodeOptions, LightNodeOptions, RelayNodeOptions } from "./types";
|
export { ContentPairProvider, useContentPair } from "./ContentPairProvider";
|
||||||
export { useContentPair } from "./useContentPair";
|
export { LightNodeOptions, RelayNodeOptions } from "./types";
|
||||||
export {
|
export { useCreateContentPair } from "./useCreatContentPair";
|
||||||
useCreateFullNode,
|
export { useCreateLightNode, useCreateRelayNode } from "./useCreateWaku";
|
||||||
useCreateLightNode,
|
export { useFilterMessages } from "./useFilterMessages";
|
||||||
useCreateRelayNode,
|
export { useLightPush } from "./useLightPush";
|
||||||
} from "./useCreateWaku";
|
export { usePeers } from "./usePeers";
|
||||||
export { useFilterSubscribe } from "./useFilterSubscribe";
|
export { useStoreMessages } from "./useStoreMessages";
|
||||||
export {
|
export { LightNodeProvider, RelayNodeProvider, useWaku } from "./WakuProvider";
|
||||||
FullNodeProvider,
|
|
||||||
LightNodeProvider,
|
|
||||||
RelayNodeProvider,
|
|
||||||
useWaku,
|
|
||||||
WakuContext,
|
|
||||||
} from "./WakuProvider";
|
|
||||||
|
|
19
src/types.ts
19
src/types.ts
|
@ -1,14 +1,15 @@
|
||||||
import { RelayCreateOptions, WakuOptions } from "@waku/core";
|
import { RelayCreateOptions, WakuOptions } from "@waku/core";
|
||||||
|
import type { Decoder, Encoder } from "@waku/core/dist/lib/message/version_0";
|
||||||
import type { CreateOptions } from "@waku/create";
|
import type { CreateOptions } from "@waku/create";
|
||||||
import type { Protocols, Waku } from "@waku/interfaces";
|
import type { Protocols, Waku } from "@waku/interfaces";
|
||||||
|
|
||||||
export type HookState = {
|
export type HookState = {
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
error: null | string;
|
error: undefined | string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CrateWakuHook<T extends Waku> = HookState & {
|
export type CrateNodeResult<T extends Waku> = HookState & {
|
||||||
node: null | T;
|
node: undefined | T;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BootstrapNodeOptions<T = {}> = {
|
export type BootstrapNodeOptions<T = {}> = {
|
||||||
|
@ -20,6 +21,12 @@ export type LightNodeOptions = CreateOptions & WakuOptions;
|
||||||
export type RelayNodeOptions = CreateOptions &
|
export type RelayNodeOptions = CreateOptions &
|
||||||
WakuOptions &
|
WakuOptions &
|
||||||
Partial<RelayCreateOptions>;
|
Partial<RelayCreateOptions>;
|
||||||
export type FullNodeOptions = CreateOptions &
|
|
||||||
WakuOptions &
|
export type ContentPair = {
|
||||||
Partial<RelayCreateOptions>;
|
encoder: Encoder;
|
||||||
|
decoder: Decoder;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ReactChildrenProps = {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
|
@ -1,26 +1,27 @@
|
||||||
import { useEffect, useState } from "react";
|
import React from "react";
|
||||||
import { createDecoder, createEncoder } from "@waku/core";
|
import { createDecoder, createEncoder } from "@waku/core";
|
||||||
import type { Decoder, Encoder } from "@waku/core/dist/lib/message/version_0";
|
import type { Decoder, Encoder } from "@waku/core/dist/lib/message/version_0";
|
||||||
|
|
||||||
type ContentPair = {
|
import type { ContentPair } from "./types";
|
||||||
encoder: null | Encoder;
|
|
||||||
decoder: null | Decoder;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates Encoder / Decoder pair for a given contentTopic.
|
* Creates Encoder / Decoder pair for a given contentTopic.
|
||||||
* @param {string} contentTopic - topic to orient to
|
* @param {string} contentTopic - topic to orient to
|
||||||
* @param {boolean} ephemeral - optional, makes messages ephemeral
|
* @param {boolean} ephemeral - makes messages ephemeral, default to false
|
||||||
* @returns {Object} Encoder / Decoder pair
|
* @returns {Object} Encoder / Decoder pair
|
||||||
*/
|
*/
|
||||||
export const useContentPair = (
|
export const useCreateContentPair = (
|
||||||
contentTopic: string,
|
contentTopic: string,
|
||||||
ephemeral?: boolean,
|
ephemeral = false,
|
||||||
): ContentPair => {
|
): ContentPair => {
|
||||||
const [encoder, setEncoder] = useState<null | Encoder>(null);
|
const [encoder, setEncoder] = React.useState<Encoder>(
|
||||||
const [decoder, setDecoder] = useState<null | Decoder>(null);
|
createEncoder(contentTopic, ephemeral),
|
||||||
|
);
|
||||||
|
const [decoder, setDecoder] = React.useState<Decoder>(
|
||||||
|
createDecoder(contentTopic),
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
React.useEffect(() => {
|
||||||
setEncoder(createEncoder(contentTopic, ephemeral));
|
setEncoder(createEncoder(contentTopic, ephemeral));
|
||||||
setDecoder(createDecoder(contentTopic));
|
setDecoder(createDecoder(contentTopic));
|
||||||
}, [contentTopic, ephemeral]);
|
}, [contentTopic, ephemeral]);
|
|
@ -1,12 +1,11 @@
|
||||||
import { useEffect, useState } from "react";
|
import React from "react";
|
||||||
import { waitForRemotePeer } from "@waku/core";
|
import { waitForRemotePeer } from "@waku/core";
|
||||||
import { createFullNode, createLightNode, createRelayNode } from "@waku/create";
|
import { createLightNode, createRelayNode } from "@waku/create";
|
||||||
import type { FullNode, LightNode, RelayNode, Waku } from "@waku/interfaces";
|
import type { LightNode, RelayNode, Waku } from "@waku/interfaces";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
BootstrapNodeOptions,
|
BootstrapNodeOptions,
|
||||||
CrateWakuHook,
|
CrateNodeResult,
|
||||||
FullNodeOptions,
|
|
||||||
LightNodeOptions,
|
LightNodeOptions,
|
||||||
RelayNodeOptions,
|
RelayNodeOptions,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
@ -19,14 +18,14 @@ type CreateNodeParams<N extends Waku, T = {}> = BootstrapNodeOptions<T> & {
|
||||||
|
|
||||||
const useCreateNode = <N extends Waku, T = {}>(
|
const useCreateNode = <N extends Waku, T = {}>(
|
||||||
params: CreateNodeParams<N, T>,
|
params: CreateNodeParams<N, T>,
|
||||||
): CrateWakuHook<N> => {
|
): CrateNodeResult<N> => {
|
||||||
const { factory, options, protocols = [] } = params;
|
const { factory, options, protocols = [] } = params;
|
||||||
|
|
||||||
const [node, setNode] = useState<N | null>(null);
|
const [node, setNode] = React.useState<N | undefined>(undefined);
|
||||||
const [isLoading, setLoading] = useState<boolean>(true);
|
const [isLoading, setLoading] = React.useState<boolean>(true);
|
||||||
const [error, setError] = useState<null | string>(null);
|
const [error, setError] = React.useState<undefined | string>(undefined);
|
||||||
|
|
||||||
useEffect(() => {
|
React.useEffect(() => {
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
|
@ -50,7 +49,8 @@ const useCreateNode = <N extends Waku, T = {}>(
|
||||||
return () => {
|
return () => {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
};
|
};
|
||||||
}, [factory, options, protocols, setNode, setLoading, setError]);
|
// TODO: missing any dependencies, it will prevent consecutive update if options change
|
||||||
|
}, []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
node,
|
node,
|
||||||
|
@ -86,17 +86,3 @@ export const useCreateRelayNode = (
|
||||||
factory: createRelayNode,
|
factory: createRelayNode,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Create Full Node helper hook.
|
|
||||||
* @param {Object} params - optional params to configure & bootstrap node
|
|
||||||
* @returns {CrateWakuHook} node, loading state and error
|
|
||||||
*/
|
|
||||||
export const useCreateFullNode = (
|
|
||||||
params?: BootstrapNodeOptions<FullNodeOptions>,
|
|
||||||
) => {
|
|
||||||
return useCreateNode<FullNode, FullNodeOptions>({
|
|
||||||
...params,
|
|
||||||
factory: createFullNode,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
import React from "react";
|
||||||
|
import type {
|
||||||
|
IDecodedMessage,
|
||||||
|
IDecoder,
|
||||||
|
IFilter,
|
||||||
|
Waku,
|
||||||
|
} from "@waku/interfaces";
|
||||||
|
|
||||||
|
import type { HookState } from "./types";
|
||||||
|
|
||||||
|
type AbstractFilterNode = Waku & {
|
||||||
|
filter: IFilter;
|
||||||
|
};
|
||||||
|
|
||||||
|
type UseFilterMessagesParams = {
|
||||||
|
node: undefined | AbstractFilterNode;
|
||||||
|
decoder: undefined | IDecoder<IDecodedMessage>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type UseFilterMessagesResult = HookState & {
|
||||||
|
messages: IDecodedMessage[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns messages from Filter subscription and keeps them up to date
|
||||||
|
* @example
|
||||||
|
* const { isLoading, error, message } = useFilterMessages({node, decoder});
|
||||||
|
* @param {Object} node - node that implements Filter, hook does nothing if undefined
|
||||||
|
* @param {Object} decoder - decoder to use for subscribing, hook does nothing if undefined
|
||||||
|
* @returns {Object} hook state (isLoading, error) and messages array
|
||||||
|
*/
|
||||||
|
export const useFilterMessages = (
|
||||||
|
params: UseFilterMessagesParams,
|
||||||
|
): UseFilterMessagesResult => {
|
||||||
|
const { node, decoder } = params;
|
||||||
|
|
||||||
|
const [error, setError] = React.useState<undefined | string>(undefined);
|
||||||
|
const [isLoading, setLoading] = React.useState<boolean>(false);
|
||||||
|
const [messages, setMessage] = React.useState<IDecodedMessage[]>([]);
|
||||||
|
|
||||||
|
const pushMessage = React.useCallback(
|
||||||
|
(message: IDecodedMessage): void => {
|
||||||
|
if (!message) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setMessage((prev) => [...prev, message]);
|
||||||
|
},
|
||||||
|
[setMessage],
|
||||||
|
);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!node || !decoder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let unsubscribe: null | (() => Promise<void>) = null;
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
node.filter
|
||||||
|
.subscribe([decoder], pushMessage)
|
||||||
|
.then((unsubscribeFn) => {
|
||||||
|
setLoading(false);
|
||||||
|
unsubscribe = unsubscribeFn;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
setLoading(false);
|
||||||
|
setError(
|
||||||
|
`Failed to subscribe to filer: ${err?.message || "no message"}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unsubscribe?.();
|
||||||
|
};
|
||||||
|
}, [node, decoder, pushMessage, setError, setLoading]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
error,
|
||||||
|
messages,
|
||||||
|
isLoading,
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,58 +0,0 @@
|
||||||
import { useCallback, useEffect, useState } from "react";
|
|
||||||
import type { IDecodedMessage, IDecoder, Waku } from "@waku/interfaces";
|
|
||||||
|
|
||||||
import type { HookState } from "./types";
|
|
||||||
|
|
||||||
type UseFilterSubscribeParams = {
|
|
||||||
waku: Waku;
|
|
||||||
decoder: IDecoder<IDecodedMessage>;
|
|
||||||
};
|
|
||||||
|
|
||||||
type UseFilterSubscribeResult = HookState & {
|
|
||||||
messages: IDecodedMessage[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useFilterSubscribe = (
|
|
||||||
params: UseFilterSubscribeParams,
|
|
||||||
): UseFilterSubscribeResult => {
|
|
||||||
const { waku, decoder } = params;
|
|
||||||
|
|
||||||
const [error, setError] = useState<null | string>(null);
|
|
||||||
const [isLoading, setLoading] = useState<boolean>(false);
|
|
||||||
const [messages, setMessage] = useState<IDecodedMessage[]>([]);
|
|
||||||
|
|
||||||
const pushMessage = useCallback(
|
|
||||||
(message: IDecodedMessage): void => {
|
|
||||||
setMessage((prev) => [...prev, message]);
|
|
||||||
},
|
|
||||||
[setMessage],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let unsubscribe: null | (() => Promise<void>) = null;
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
waku?.filter
|
|
||||||
?.subscribe([decoder], pushMessage)
|
|
||||||
.then((unsubscribeFn) => {
|
|
||||||
setLoading(false);
|
|
||||||
unsubscribe = unsubscribeFn;
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
setLoading(false);
|
|
||||||
setError(
|
|
||||||
`Failed to subscribe to filer: ${err?.message || "no message"}`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
unsubscribe?.();
|
|
||||||
};
|
|
||||||
}, [waku, decoder, pushMessage, setError, setLoading]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
error,
|
|
||||||
messages,
|
|
||||||
isLoading,
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
import React from "react";
|
||||||
|
import type {
|
||||||
|
IEncoder,
|
||||||
|
ILightPush,
|
||||||
|
IMessage,
|
||||||
|
ProtocolOptions,
|
||||||
|
SendResult,
|
||||||
|
Waku,
|
||||||
|
} from "@waku/interfaces";
|
||||||
|
|
||||||
|
type AbstractLightPushNode = Waku & {
|
||||||
|
lightPush: ILightPush;
|
||||||
|
};
|
||||||
|
|
||||||
|
type UseLightPushParams = {
|
||||||
|
encoder: undefined | IEncoder;
|
||||||
|
node: undefined | AbstractLightPushNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
type PushFn = (
|
||||||
|
message: IMessage,
|
||||||
|
opts?: ProtocolOptions,
|
||||||
|
) => Promise<SendResult>;
|
||||||
|
|
||||||
|
type UseLightPushResult = {
|
||||||
|
push?: undefined | PushFn;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns light push methods bound to node and encoder
|
||||||
|
* @param {Object} params.node - node that implements ILightPush, hook does nothing if empty
|
||||||
|
* @param {Object} params.encoder - encoder for processing messages, hook does nothing if empty
|
||||||
|
* @returns {Object} methods of ILightPush such as push
|
||||||
|
*/
|
||||||
|
export const useLightPush = (
|
||||||
|
params: UseLightPushParams,
|
||||||
|
): UseLightPushResult => {
|
||||||
|
const { node, encoder } = params;
|
||||||
|
|
||||||
|
const push = React.useCallback<PushFn>(
|
||||||
|
(message, opts = undefined) => {
|
||||||
|
return node!.lightPush.push(encoder as IEncoder, message, opts);
|
||||||
|
},
|
||||||
|
[node, encoder],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!node && !encoder) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
push,
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,71 @@
|
||||||
|
import React from "react";
|
||||||
|
import type {
|
||||||
|
Peer,
|
||||||
|
PeerProtocolsChangeData,
|
||||||
|
} from "@libp2p/interface-peer-store";
|
||||||
|
import type { Waku } from "@waku/interfaces";
|
||||||
|
|
||||||
|
type UsePeersParams = {
|
||||||
|
node: undefined | Waku;
|
||||||
|
};
|
||||||
|
|
||||||
|
type UsePeersResults = {
|
||||||
|
storePeers?: undefined | Peer[];
|
||||||
|
filterPeers?: undefined | Peer[];
|
||||||
|
lightPushPeers?: undefined | Peer[];
|
||||||
|
peerExchangePeers?: undefined | Peer[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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>({});
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!node) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const listener = async (_event: CustomEvent<PeerProtocolsChangeData>) => {
|
||||||
|
const peers = await Promise.all([
|
||||||
|
handleCatch(node?.store?.peers()),
|
||||||
|
handleCatch(node?.filter?.peers()),
|
||||||
|
handleCatch(node?.lightPush?.peers()),
|
||||||
|
handleCatch(node?.peerExchange?.peers()),
|
||||||
|
]);
|
||||||
|
|
||||||
|
setPeers({
|
||||||
|
storePeers: peers[0],
|
||||||
|
filterPeers: peers[1],
|
||||||
|
lightPushPeers: peers[2],
|
||||||
|
peerExchangePeers: peers[3],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
node.libp2p.peerStore.addEventListener("change:protocols", listener);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
node.libp2p.peerStore.removeEventListener("change:protocols", listener);
|
||||||
|
};
|
||||||
|
}, [node, setPeers]);
|
||||||
|
|
||||||
|
return peers;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: handle error in case fetching of peers failed
|
||||||
|
function handleCatch(promise?: Promise<Peer[]>): Promise<Peer[] | undefined> {
|
||||||
|
if (!promise) {
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise.catch((_) => {
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
import React from "react";
|
||||||
|
import type {
|
||||||
|
IDecodedMessage,
|
||||||
|
IDecoder,
|
||||||
|
IStore,
|
||||||
|
StoreQueryOptions,
|
||||||
|
Waku,
|
||||||
|
} from "@waku/interfaces";
|
||||||
|
|
||||||
|
import type { HookState } from "./types";
|
||||||
|
|
||||||
|
type AbstractStoreNode = Waku & {
|
||||||
|
store: IStore;
|
||||||
|
};
|
||||||
|
|
||||||
|
type UseStoreMessagesParams = {
|
||||||
|
node: undefined | AbstractStoreNode;
|
||||||
|
decoder: undefined | IDecoder<IDecodedMessage>;
|
||||||
|
options: StoreQueryOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
type UseStoreMessagesResult = HookState & {
|
||||||
|
messages: IDecodedMessage[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook for retrieving messages from Store protocol based on options
|
||||||
|
* @example
|
||||||
|
* const { isLoading, error, messages } = useStoreMessages({node, decoder, options});
|
||||||
|
* @param {Object} node - node that implement Store, hook does nothing if undefined
|
||||||
|
* @param {Object} decoder - decoder to use for getting messages, hook does nothing if undefined
|
||||||
|
* @param {StoreQueryOptions} options - options to initiate query to get messages
|
||||||
|
* @returns {Object} hook state (isLoading, error) and messages array
|
||||||
|
*/
|
||||||
|
export const useStoreMessages = (
|
||||||
|
params: UseStoreMessagesParams,
|
||||||
|
): UseStoreMessagesResult => {
|
||||||
|
const { node, decoder, options } = params;
|
||||||
|
|
||||||
|
const [error, setError] = React.useState<undefined | string>(undefined);
|
||||||
|
const [isLoading, setLoading] = React.useState<boolean>(false);
|
||||||
|
const [messages, setMessage] = React.useState<IDecodedMessage[]>([]);
|
||||||
|
|
||||||
|
const pushMessage = React.useCallback(
|
||||||
|
(newMessages: IDecodedMessage[]): void => {
|
||||||
|
if (!newMessages || !newMessages.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setMessage((prev) => [...prev, ...newMessages]);
|
||||||
|
},
|
||||||
|
[setMessage],
|
||||||
|
);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!node || !decoder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cancelled = false;
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
Promise.resolve()
|
||||||
|
.then(async () => {
|
||||||
|
for await (const promises of node.store.queryGenerator(
|
||||||
|
[decoder],
|
||||||
|
options,
|
||||||
|
)) {
|
||||||
|
if (cancelled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const messagesRaw = await Promise.all(promises);
|
||||||
|
const filteredMessages = messagesRaw.filter(
|
||||||
|
(v): v is IDecodedMessage => !!v,
|
||||||
|
);
|
||||||
|
|
||||||
|
pushMessage(filteredMessages);
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(false);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
setLoading(false);
|
||||||
|
setError(
|
||||||
|
`Failed to query messages from store: ${
|
||||||
|
err?.message || "no message"
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
// TODO: missing dependency on options, it will prevent consecutive update if options change
|
||||||
|
}, [node, decoder, pushMessage, setError, setLoading]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
error,
|
||||||
|
isLoading,
|
||||||
|
messages,
|
||||||
|
};
|
||||||
|
};
|
Loading…
Reference in New Issue