Skip to content

Commit 3c58752

Browse files
authored
Align subscription error handling (#2296)
1 parent a30bf6f commit 3c58752

File tree

2 files changed

+70
-31
lines changed

2 files changed

+70
-31
lines changed

packages/fcl-core/src/events/index.test.ts

Lines changed: 51 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ describe("events", () => {
7676

7777
test("subscribe should pipe the events to the callback", async () => {
7878
const filter = {eventTypes: ["A"]}
79-
const callback = jest.fn()
79+
const onData = jest.fn()
80+
const onError = jest.fn()
8081

8182
const mockEvents = [{type: "A"}, {type: "B"}] as any[]
8283

@@ -85,59 +86,82 @@ describe("events", () => {
8586
return mockSubscription
8687
})
8788

88-
events(filter).subscribe(callback)
89+
events(filter).subscribe(onData, onError)
8990

9091
// Flush the event loop
9192
await new Promise(resolve => setTimeout(resolve, 0))
9293

93-
expect(callback.mock.calls).toEqual([
94-
[mockEvents[0], null],
95-
[mockEvents[1], null],
96-
])
94+
expect(onData.mock.calls).toEqual([[{type: "A"}], [{type: "B"}]])
95+
expect(onError).not.toHaveBeenCalled()
9796
})
9897

9998
test("subscribe should pipe the errors to the callback", async () => {
10099
const filter = {eventTypes: ["A"]}
101-
const callback = jest.fn()
100+
const onData = jest.fn()
101+
const onError = jest.fn()
102102

103103
const mockError = new Error("mock error")
104104

105-
jest.mocked(subscribe).mockImplementation(({onError}) => {
106-
onError(mockError)
105+
jest.mocked(subscribe).mockImplementation(({onError: _onError}) => {
106+
_onError(mockError)
107107
return mockSubscription
108108
})
109109

110-
events(filter).subscribe(callback)
110+
events(filter).subscribe(onData, onError)
111111

112112
// Flush the event loop
113113
await new Promise(resolve => setTimeout(resolve, 0))
114114

115-
expect(callback.mock.calls).toEqual([[null, mockError]])
115+
expect(onData).not.toHaveBeenCalled()
116+
expect(onError).toHaveBeenCalledTimes(1)
117+
expect(onError).toHaveBeenCalledWith(mockError)
116118
})
117119

118120
test("fallback to legacy polling if subscriptions are not supported", async () => {
119121
const filter = "A"
120-
const callback = jest.fn()
122+
const onData = jest.fn()
123+
const onError = jest.fn()
121124

122-
const mockError = new SubscriptionsNotSupportedError()
125+
const mockNotSupportedError = new SubscriptionsNotSupportedError()
123126

124-
jest.mocked(subscribe).mockImplementation(({onError}) => {
125-
onError(mockError)
127+
jest.mocked(subscribe).mockImplementation(({onError: _onError}) => {
128+
_onError(mockNotSupportedError)
126129
return mockSubscription
127130
})
128131

129-
const unsubscribe = events(filter).subscribe(callback)
132+
const unsubscribe = events(filter).subscribe(onData, onError)
130133

131134
// Flush the event loop
132135
await new Promise(resolve => setTimeout(resolve, 0))
133136

134-
// Check that the callback was called
135-
expect(callback).toHaveBeenCalledTimes(0)
136-
expect(subscribe).toHaveBeenCalledTimes(1)
137+
// Check that the error did not propagate to the onError callback
138+
expect(onData).not.toHaveBeenCalled()
139+
expect(onError).not.toHaveBeenCalled()
137140

138-
// Check that legacy polling was called
141+
// Check that the legacy subscribe was called
142+
expect(mockLegacySubscribeObject.subscribe).toHaveBeenCalledTimes(1)
143+
expect(mockLegacyUnsubscribe).not.toHaveBeenCalled()
144+
145+
// Check that the legacy events were called with the correct filter
139146
expect(legacyEvents).toHaveBeenCalledWith(filter)
140-
expect(mockLegacySubscribeObject.subscribe).toHaveBeenCalledWith(callback)
147+
expect(mockLegacySubscribeObject.subscribe).toHaveBeenCalledWith(
148+
expect.any(Function)
149+
)
150+
151+
// Mock the legacy subscribe to call the onData callback
152+
const legacyOnData = (
153+
mockLegacySubscribeObject.subscribe.mock.calls as any
154+
)[0][0] as jest.Mock
155+
const mockLegacyEvents = [{type: "A"}, {type: "B"}] as any[]
156+
mockLegacyEvents.forEach(event => legacyOnData(event))
157+
158+
// Flush the event loop
159+
await new Promise(resolve => setTimeout(resolve, 0))
160+
161+
// Check that the onData callback was called with the legacy events
162+
expect(onData.mock.calls).toEqual([[{type: "A"}], [{type: "B"}]])
163+
expect(onError).not.toHaveBeenCalled()
164+
expect(mockLegacyUnsubscribe).not.toHaveBeenCalled()
141165

142166
// Unsubscribe
143167
unsubscribe()
@@ -151,16 +175,19 @@ describe("events", () => {
151175

152176
test("emulator should fallback to legacy polling", async () => {
153177
const filter = "A"
154-
const callback = jest.fn()
178+
const onData = jest.fn()
179+
const onError = jest.fn()
155180

156181
jest.mocked(getChainId).mockResolvedValue("local")
157182

158-
events(filter).subscribe(callback)
183+
events(filter).subscribe(onData, onError)
159184

160185
// Flush the event loop
161186
await new Promise(resolve => setTimeout(resolve, 10))
162187

163188
expect(legacyEvents).toHaveBeenCalledWith(filter)
164-
expect(mockLegacySubscribeObject.subscribe).toHaveBeenCalledWith(callback)
189+
expect(mockLegacySubscribeObject.subscribe).toHaveBeenCalledWith(
190+
expect.any(Function)
191+
)
165192
})
166193
})

packages/fcl-core/src/events/index.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,11 @@ export function events(filterOrType?: EventFilter | string) {
2525

2626
return {
2727
subscribe: (
28-
callback: (events: Event | null, error: Error | null) => void
29-
) => {
28+
onData: (event: Event) => void,
29+
onError: (error: Error) => void = (error: Error) => {
30+
console.error("Unhandled error in event subscription:", error)
31+
}
32+
): (() => void) => {
3033
let unsubscribeFn = () => {}
3134
let unsubscribeFnLegacy = () => {}
3235

@@ -35,8 +38,9 @@ export function events(filterOrType?: EventFilter | string) {
3538
const {unsubscribe} = subscribe({
3639
topic: SubscriptionTopic.EVENTS,
3740
args: filter,
38-
onData: data => {
39-
callback(data, null)
41+
onData: event => {
42+
// Emit the event
43+
onData(event)
4044
},
4145
onError: (error: Error) => {
4246
// If subscriptions are not supported, fallback to legacy polling, otherwise return the error
@@ -46,7 +50,7 @@ export function events(filterOrType?: EventFilter | string) {
4650
)
4751
fallbackLegacyPolling()
4852
} else {
49-
callback(null, error)
53+
onError(error)
5054
}
5155
},
5256
})
@@ -60,7 +64,15 @@ export function events(filterOrType?: EventFilter | string) {
6064
"Legacy fcl.events fallback only supports string filters (single event type)"
6165
)
6266
}
63-
unsubscribeFnLegacy = legacyEvents(filterOrType).subscribe(callback)
67+
unsubscribeFnLegacy = legacyEvents(filterOrType).subscribe(
68+
(event: Event, error?: Error) => {
69+
if (error) {
70+
onError(error)
71+
} else {
72+
onData(event)
73+
}
74+
}
75+
)
6476
}
6577

6678
async function subscribeToEvents() {
@@ -80,7 +92,7 @@ export function events(filterOrType?: EventFilter | string) {
8092

8193
// Subscribe to events
8294
const initPromise = subscribeToEvents().catch(error => {
83-
callback(null, error)
95+
onError(error)
8496
})
8597

8698
// Return an unsubscribe function

0 commit comments

Comments
 (0)