-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathuseSubscription.ts
More file actions
117 lines (106 loc) · 3.03 KB
/
useSubscription.ts
File metadata and controls
117 lines (106 loc) · 3.03 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
115
116
117
import { xdr } from "@stellar/stellar-sdk"
import { Server, type Api } from "@stellar/stellar-sdk/rpc"
import * as React from "react"
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 for a given topic from a given contract, using a library
* generated with `soroban contract bindings typescript`.
*
* Someday such generated libraries will include functions for subscribing to
* the events the contract emits, but for now you can copy this hook into your
* React project if you need to subscribe to events, or adapt this logic for
* non-React use.
*/
export function useSubscription(
contractId: string,
topic: string,
onEvent: (event: Api.EventResponse) => void,
pollInterval = 5000,
) {
const id = `${contractId}:${topic}`
if (!paging[id]) paging[id] = {}
const page = paging[id]
React.useEffect(() => {
let timeoutId: NodeJS.Timeout | null = null
let stop = false
async function pollEvents(): Promise<void> {
try {
if (!page.lastLedgerStart) {
const latestLedgerState = await server.getLatestLedger()
page.lastLedgerStart = latestLedgerState.sequence
}
const lastLedger = page.lastLedgerStart
const response = await server.getEvents(
page.pagingToken
? {
cursor: page.pagingToken,
filters: [
{
contractIds: [contractId],
topics: [[xdr.ScVal.scvSymbol(topic).toXDR("base64")]],
type: "contract",
},
],
limit: 10,
}
: {
startLedger: lastLedger,
endLedger: lastLedger + 100,
filters: [
{
contractIds: [contractId],
topics: [[xdr.ScVal.scvSymbol(topic).toXDR("base64")]],
type: "contract",
},
],
limit: 10,
},
)
page.pagingToken = undefined
if (response.latestLedger) {
page.lastLedgerStart = response.latestLedger
}
if (response.events && response.events.length > 0) {
response.events.forEach((event) => {
try {
onEvent(event)
} catch (error) {
console.error(
"Poll Events: subscription callback had error: ",
error,
)
}
})
if (response.cursor) {
page.pagingToken = response.cursor
}
}
} 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, onEvent, id, pollInterval])
}