From 1baedb1f8785ed3806ca992eb7f7ebd42be5b26c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Dan?= Date: Mon, 27 Nov 2023 16:23:36 +0100 Subject: [PATCH] feat: use subscription to listen for certificates --- src/__generated__/gql.ts | 5 + src/__generated__/graphql.ts | 10 +- .../useSubnetSubscribeToCertificates.tsx | 140 ++---------------- 3 files changed, 29 insertions(+), 126 deletions(-) diff --git a/src/__generated__/gql.ts b/src/__generated__/gql.ts index 04f6060..03cb8b4 100644 --- a/src/__generated__/gql.ts +++ b/src/__generated__/gql.ts @@ -15,6 +15,7 @@ import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/ const documents = { "\n query Certificate($certificateId: CertificateId!) {\n certificate(certificateId: $certificateId) {\n prevId\n id\n proof\n signature\n sourceSubnetId {\n value\n }\n stateRoot\n targetSubnets {\n value\n }\n receiptsRootHash\n txRootHash\n verifier\n }\n }\n": types.CertificateDocument, "\n query Certificates($fromSourceCheckpoint: SourceCheckpoint!, $limit: Int!) {\n certificates(fromSourceCheckpoint: $fromSourceCheckpoint, first: $limit) {\n prevId\n id\n proof\n signature\n sourceSubnetId {\n value\n }\n stateRoot\n targetSubnets {\n value\n }\n receiptsRootHash\n txRootHash\n verifier\n }\n }\n": types.CertificatesDocument, + "\n subscription OnCertificates($filter: SubnetFilter) {\n watchDeliveredCertificates(filter: $filter) {\n prevId\n id\n proof\n signature\n sourceSubnetId {\n value\n }\n stateRoot\n targetSubnets {\n value\n }\n receiptsRootHash\n txRootHash\n verifier\n }\n }\n": types.OnCertificatesDocument, }; /** @@ -39,6 +40,10 @@ export function graphql(source: "\n query Certificate($certificateId: Certifica * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql(source: "\n query Certificates($fromSourceCheckpoint: SourceCheckpoint!, $limit: Int!) {\n certificates(fromSourceCheckpoint: $fromSourceCheckpoint, first: $limit) {\n prevId\n id\n proof\n signature\n sourceSubnetId {\n value\n }\n stateRoot\n targetSubnets {\n value\n }\n receiptsRootHash\n txRootHash\n verifier\n }\n }\n"): (typeof documents)["\n query Certificates($fromSourceCheckpoint: SourceCheckpoint!, $limit: Int!) {\n certificates(fromSourceCheckpoint: $fromSourceCheckpoint, first: $limit) {\n prevId\n id\n proof\n signature\n sourceSubnetId {\n value\n }\n stateRoot\n targetSubnets {\n value\n }\n receiptsRootHash\n txRootHash\n verifier\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n subscription OnCertificates($filter: SubnetFilter) {\n watchDeliveredCertificates(filter: $filter) {\n prevId\n id\n proof\n signature\n sourceSubnetId {\n value\n }\n stateRoot\n targetSubnets {\n value\n }\n receiptsRootHash\n txRootHash\n verifier\n }\n }\n"): (typeof documents)["\n subscription OnCertificates($filter: SubnetFilter) {\n watchDeliveredCertificates(filter: $filter) {\n prevId\n id\n proof\n signature\n sourceSubnetId {\n value\n }\n stateRoot\n targetSubnets {\n value\n }\n receiptsRootHash\n txRootHash\n verifier\n }\n }\n"]; export function graphql(source: string) { return (documents as any)[source] ?? {}; diff --git a/src/__generated__/graphql.ts b/src/__generated__/graphql.ts index 36d0305..666ef4f 100644 --- a/src/__generated__/graphql.ts +++ b/src/__generated__/graphql.ts @@ -110,6 +110,14 @@ export type CertificatesQueryVariables = Exact<{ export type CertificatesQuery = { __typename?: 'QueryRoot', certificates: Array<{ __typename?: 'Certificate', prevId: string, id: string, proof: string, signature: string, stateRoot: string, receiptsRootHash: string, txRootHash: string, verifier: number, sourceSubnetId: { __typename?: 'SubnetId', value: string }, targetSubnets: Array<{ __typename?: 'SubnetId', value: string }> }> }; +export type OnCertificatesSubscriptionVariables = Exact<{ + filter?: InputMaybe; +}>; + + +export type OnCertificatesSubscription = { __typename?: 'SubscriptionRoot', watchDeliveredCertificates: { __typename?: 'Certificate', prevId: string, id: string, proof: string, signature: string, stateRoot: string, receiptsRootHash: string, txRootHash: string, verifier: number, sourceSubnetId: { __typename?: 'SubnetId', value: string }, targetSubnets: Array<{ __typename?: 'SubnetId', value: string }> } }; + export const CertificateDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Certificate"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"certificateId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CertificateId"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"certificate"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"certificateId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"certificateId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"prevId"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"proof"}},{"kind":"Field","name":{"kind":"Name","value":"signature"}},{"kind":"Field","name":{"kind":"Name","value":"sourceSubnetId"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"value"}}]}},{"kind":"Field","name":{"kind":"Name","value":"stateRoot"}},{"kind":"Field","name":{"kind":"Name","value":"targetSubnets"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"value"}}]}},{"kind":"Field","name":{"kind":"Name","value":"receiptsRootHash"}},{"kind":"Field","name":{"kind":"Name","value":"txRootHash"}},{"kind":"Field","name":{"kind":"Name","value":"verifier"}}]}}]}}]} as unknown as DocumentNode; -export const CertificatesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Certificates"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"fromSourceCheckpoint"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SourceCheckpoint"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"certificates"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"fromSourceCheckpoint"},"value":{"kind":"Variable","name":{"kind":"Name","value":"fromSourceCheckpoint"}}},{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"prevId"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"proof"}},{"kind":"Field","name":{"kind":"Name","value":"signature"}},{"kind":"Field","name":{"kind":"Name","value":"sourceSubnetId"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"value"}}]}},{"kind":"Field","name":{"kind":"Name","value":"stateRoot"}},{"kind":"Field","name":{"kind":"Name","value":"targetSubnets"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"value"}}]}},{"kind":"Field","name":{"kind":"Name","value":"receiptsRootHash"}},{"kind":"Field","name":{"kind":"Name","value":"txRootHash"}},{"kind":"Field","name":{"kind":"Name","value":"verifier"}}]}}]}}]} as unknown as DocumentNode; \ No newline at end of file +export const CertificatesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Certificates"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"fromSourceCheckpoint"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SourceCheckpoint"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"certificates"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"fromSourceCheckpoint"},"value":{"kind":"Variable","name":{"kind":"Name","value":"fromSourceCheckpoint"}}},{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"prevId"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"proof"}},{"kind":"Field","name":{"kind":"Name","value":"signature"}},{"kind":"Field","name":{"kind":"Name","value":"sourceSubnetId"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"value"}}]}},{"kind":"Field","name":{"kind":"Name","value":"stateRoot"}},{"kind":"Field","name":{"kind":"Name","value":"targetSubnets"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"value"}}]}},{"kind":"Field","name":{"kind":"Name","value":"receiptsRootHash"}},{"kind":"Field","name":{"kind":"Name","value":"txRootHash"}},{"kind":"Field","name":{"kind":"Name","value":"verifier"}}]}}]}}]} as unknown as DocumentNode; +export const OnCertificatesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"subscription","name":{"kind":"Name","value":"OnCertificates"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"SubnetFilter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"watchDeliveredCertificates"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"prevId"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"proof"}},{"kind":"Field","name":{"kind":"Name","value":"signature"}},{"kind":"Field","name":{"kind":"Name","value":"sourceSubnetId"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"value"}}]}},{"kind":"Field","name":{"kind":"Name","value":"stateRoot"}},{"kind":"Field","name":{"kind":"Name","value":"targetSubnets"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"value"}}]}},{"kind":"Field","name":{"kind":"Name","value":"receiptsRootHash"}},{"kind":"Field","name":{"kind":"Name","value":"txRootHash"}},{"kind":"Field","name":{"kind":"Name","value":"verifier"}}]}}]}}]} as unknown as DocumentNode; \ No newline at end of file diff --git a/src/hooks/useSubnetSubscribeToCertificates.tsx b/src/hooks/useSubnetSubscribeToCertificates.tsx index 4cb2d23..fd9bf4b 100644 --- a/src/hooks/useSubnetSubscribeToCertificates.tsx +++ b/src/hooks/useSubnetSubscribeToCertificates.tsx @@ -1,16 +1,13 @@ -import { useQuery } from '@apollo/client' -import { useEffect, useMemo, useRef, useState } from 'react' +import { useSubscription } from '@apollo/client' +import { useEffect, useState } from 'react' import { graphql } from '../__generated__/gql' -// import { ErrorsContext } from '../contexts/errors' import { Certificate } from '../types' -import { SourceStreamPosition } from '../__generated__/graphql' +import { SubnetFilter } from '../__generated__/graphql' -const DEFAULT_LIMIT = 10 - -const GET_CERTIFICATES = graphql(` - query Certificates($fromSourceCheckpoint: SourceCheckpoint!, $limit: Int!) { - certificates(fromSourceCheckpoint: $fromSourceCheckpoint, first: $limit) { +const WATCH_CERTIFICATES = graphql(` + subscription OnCertificates($filter: SubnetFilter) { + watchDeliveredCertificates(filter: $filter) { prevId id proof @@ -30,130 +27,23 @@ const GET_CERTIFICATES = graphql(` `) interface Options { - limit?: number - sourceSubnetIds?: Array + filter?: SubnetFilter } -export default function useSubnetSubscribeToCertificates({ - limit, - sourceSubnetIds, -}: Options) { - // const { setErrors } = React.useContext(ErrorsContext) +export default function useSubnetSubscribeToCertificates({ filter }: Options) { const [certificates, setCertificates] = useState([]) - const [currentIndexes, setCurrentIndexes] = useState( - new Map() - ) - const currentIndexesRef = useRef | null>(null) - const [storedPositions, setStoredPositions] = useState( - new Map() - ) - const uniqueSourceSubnetId = useRef(null) - const definedLimit = useMemo(() => limit || DEFAULT_LIMIT, [limit]) - - useEffect( - function storeLatestCurrentIndexes() { - window.setTimeout(() => { - currentIndexesRef.current = currentIndexes - }, 500) - }, - [currentIndexes] - ) - - useEffect( - function onNewSourceSubnetIds() { - let newStoredPositions = storedPositions - let newCurrentIndexes = currentIndexes - - sourceSubnetIds?.forEach(({ position, sourceSubnetId }) => { - if ( - storedPositions.get(sourceSubnetId.value) === undefined && - position !== undefined - ) { - newStoredPositions.set(sourceSubnetId.value, position) - newCurrentIndexes.set(sourceSubnetId.value, 0) - } - }) - - // Temp: clean certificates when subscription is to one subnet only and that subnet changes - if ( - sourceSubnetIds?.length === 1 && - sourceSubnetIds[0].sourceSubnetId.value !== uniqueSourceSubnetId.current - ) { - uniqueSourceSubnetId.current = sourceSubnetIds[0].sourceSubnetId.value - newStoredPositions = new Map() - newCurrentIndexes = new Map() - newCurrentIndexes.set(sourceSubnetIds[0].sourceSubnetId.value, 0) - setCertificates([]) - } - - setStoredPositions(newStoredPositions) - setCurrentIndexes(newCurrentIndexes) - }, - [sourceSubnetIds] - ) - const { data, error, loading } = useQuery(GET_CERTIFICATES, { + const { data, error, loading } = useSubscription(WATCH_CERTIFICATES, { variables: { - fromSourceCheckpoint: { - sourceSubnetIds: Array.from(storedPositions).map(([id]) => ({ - value: id, - })), - positions: Array.from(storedPositions).map(([id, position]) => { - return { - sourceSubnetId: { value: id }, - position: position - ? (currentIndexesRef.current?.get(id) || 0) + position - : Infinity, - } - }), - }, - limit: definedLimit, + filter, }, - pollInterval: 2000, }) - useEffect( - function appendCertificate() { - const latestCurrentIndexes = currentIndexesRef.current - - if ( - data?.certificates && - data?.certificates.length && - latestCurrentIndexes - ) { - const newCurrentIndexes = latestCurrentIndexes - const newCertificates: Certificate[] = [] - - data.certificates.forEach((certificate) => { - const currentIndex = newCurrentIndexes.get( - certificate.sourceSubnetId.value - ) - - if (currentIndex !== undefined) { - newCurrentIndexes.set( - certificate.sourceSubnetId.value, - currentIndex + 1 - ) - - const sourcePosition = storedPositions.get( - certificate.sourceSubnetId.value - ) - newCertificates.push({ - ...certificate, - position: sourcePosition - ? sourcePosition + currentIndex - : undefined, - }) - } - }) - - setCurrentIndexes(newCurrentIndexes) - setCertificates((c) => [...newCertificates, ...c]) - } - }, - [data?.certificates] - ) - console.log(certificates) + useEffect(() => { + if (data && data.watchDeliveredCertificates) { + setCertificates((c) => [data.watchDeliveredCertificates, ...c]) + } + }, [data]) return { certificates, error, loading } }