forked from Prompt-Hash-Stellar/prompt-hash
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathuseSubscription.ts
More file actions
114 lines (101 loc) · 3.31 KB
/
Copy pathuseSubscription.ts
File metadata and controls
114 lines (101 loc) · 3.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import * as React from "react";
import { Server, Api } from "@stellar/stellar-sdk/rpc";
import { xdr } from "@stellar/stellar-sdk";
import { rpcUrl, stellarNetwork } from "../contracts/util";
/**
* Concatenated `${contractId}:${topic ?? "*"}`
*/
type PagingKey = string;
/**
* Paging tokens for each contract/topic pair. These can be mutated directly,
* rather than being stored as state within the React hook.
*/
const paging: Record<
PagingKey,
{ lastLedgerStart?: number; pagingToken?: string }
> = {};
// NOTE: Server is configured using envvars which shouldn't change during runtime
const server = new Server(rpcUrl, { allowHttp: stellarNetwork === "LOCAL" });
/**
* Subscribe to events from a given contract, optionally filtered by topic.
*
* When `topic` is omitted, all events from the contract are delivered.
* The `onEvent` callback is held in a ref so the poll loop is not restarted
* when the callback identity changes between renders.
*/
export function useSubscription(
contractId: string,
topic: string | undefined,
onEvent: (_event: Api.EventResponse) => void,
pollInterval = 5000,
) {
const id = `${contractId}:${topic ?? "*"}`;
// Stable ref so the poll loop doesn't restart when onEvent identity changes.
const onEventRef = React.useRef(onEvent);
React.useLayoutEffect(() => {
onEventRef.current = onEvent;
});
React.useEffect(() => {
// Don't start polling when contract is not configured.
if (!contractId) return;
paging[id] = paging[id] || {};
let timeoutId: NodeJS.Timeout | null = null;
let stop = false;
async function pollEvents(): Promise<void> {
try {
if (!paging[id].lastLedgerStart) {
const latestLedgerState = await server.getLatestLedger();
paging[id].lastLedgerStart = latestLedgerState.sequence;
}
const topicFilter = topic
? { topics: [[xdr.ScVal.scvSymbol(topic).toXDR("base64")]] }
: {};
// @ts-ignore
const response = await server.getEvents({
startLedger: !paging[id].pagingToken
? paging[id].lastLedgerStart
: undefined,
cursor: paging[id].pagingToken as string,
filters: [
{
contractIds: [contractId],
...topicFilter,
type: "contract",
},
],
limit: 10,
});
paging[id].pagingToken = undefined;
if (response.latestLedger) {
paging[id].lastLedgerStart = response.latestLedger;
}
if (response.events) {
response.events.forEach((event) => {
try {
onEventRef.current(event);
} catch (error) {
console.error(
"Poll Events: subscription callback had error: ",
error,
);
} finally {
// @ts-ignore
paging[id].pagingToken = event.pagingToken;
}
});
}
} catch (error) {
console.error("Poll Events: error: ", error);
} finally {
if (!stop) {
timeoutId = setTimeout(() => void pollEvents(), pollInterval);
}
}
}
void pollEvents();
return () => {
if (timeoutId != null) clearTimeout(timeoutId);
stop = true;
};
}, [contractId, topic, id, pollInterval]);
}