Skip to content

Commit 563c092

Browse files
committed
Add SDK SubscriptionManager
1 parent 426bf40 commit 563c092

File tree

4 files changed

+204
-0
lines changed

4 files changed

+204
-0
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { config } from "@onflow/config"
2+
import {subscribe} from "./subscribe"
3+
import {SubscriptionManager} from "./subscription-manager"
4+
5+
const MockSubscriptionManager = <jest.Mock<SubscriptionManager>>SubscriptionManager;
6+
7+
jest.mock("./subscription-manager")
8+
9+
describe("subscribe", () => {
10+
let mockSubscriptionTransport: jest.Mock
11+
12+
beforeEach(() => {
13+
jest.resetAllMocks()
14+
mockSubscriptionTransport = jest.fn().mockImplementation(() => ({
15+
subscribe: jest.fn().mockReturnValue({
16+
unsubscribe: jest.fn()
17+
})
18+
}))
19+
})
20+
21+
test("subscribes to a topic and returns a subscription", async () => {
22+
const topic = "topic"
23+
const args = {foo: "bar"}
24+
const onData = jest.fn()
25+
const onError = jest.fn()
26+
27+
const sub = await config().overload({
28+
"sdk.subscriptionTransport": mockSubscriptionTransport,
29+
"accessNode.api": "http://localhost:8080"
30+
}, async () => {
31+
await subscribe({topic, args, onData, onError})
32+
})
33+
34+
expect(MockSubscriptionManager).toHaveBeenCalledTimes(1)
35+
expect(mockSubscriptionTransport).toHaveBeenCalledTimes(1)
36+
expect(mockSubscriptionTransport).toHaveBeenCalledWith({node: "http://localhost:8080"})
37+
38+
const manager = <jest.Mocked<SubscriptionManager>>MockSubscriptionManager.mock.instances[0]
39+
expect(manager.subscribe).toHaveBeenCalledTimes(1)
40+
expect(manager.subscribe).toHaveBeenCalledWith({
41+
topic,
42+
args,
43+
onData,
44+
onError
45+
})
46+
expect(sub).toBe(manager.subscribe.mock.results[0].value)
47+
})
48+
})
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import {config} from "@onflow/config"
2+
import {
3+
SubscriptionManager,
4+
SubscriptionTransportCstr,
5+
} from "./subscription-manager"
6+
7+
const managerMap: Map<
8+
SubscriptionTransportCstr,
9+
{
10+
[node: string]: SubscriptionManager
11+
}
12+
> = new Map()
13+
14+
// TODO: OPTS FUNCTION
15+
export async function subscribe(
16+
{
17+
topic,
18+
args,
19+
onData,
20+
onError,
21+
}: {
22+
topic: string
23+
args: any
24+
onData: (data: any) => void
25+
onError: (error: Error) => void
26+
}
27+
) {
28+
// Find the SubscriptionManager instance corresponding to the current transport and node
29+
const subscriptionTransport = (await config().get<SubscriptionTransportCstr>("sdk.subscriptionTransport"))
30+
const node = await config().get<string>("accessNode.api")
31+
32+
const managersForConstructor = managerMap.get(subscriptionTransport)
33+
const subscriptionManager =
34+
managersForConstructor?.[node] ??
35+
new SubscriptionManager(new subscriptionTransport({node}))
36+
managerMap.set(subscriptionTransport, {
37+
...managersForConstructor,
38+
[node]: subscriptionManager,
39+
})
40+
41+
// TODO: handle onError
42+
// Subscribe using the resolved transport
43+
return subscriptionManager.subscribe({topic, args, onData, onError})
44+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { SubscriptionManager, SubscriptionTransport } from "./subscription-manager"
2+
3+
describe("SubscriptionManager", () => {
4+
let mockSubscriptionTransport: jest.Mocked<SubscriptionTransport>
5+
let manager: SubscriptionManager
6+
7+
beforeEach(() => {
8+
jest.resetAllMocks()
9+
mockSubscriptionTransport = {
10+
subscribe: jest.fn().mockReturnValue({
11+
unsubscribe: jest.fn()
12+
})
13+
}
14+
manager = new SubscriptionManager(mockSubscriptionTransport)
15+
})
16+
17+
test("subscribes to a topic and returns a subscription", async () => {
18+
const topic = "topic"
19+
const args = {foo: "bar"}
20+
const onData = jest.fn()
21+
const onError = jest.fn()
22+
23+
const sub = manager.subscribe({topic, args, onData, onError})
24+
25+
expect(mockSubscriptionTransport.subscribe).toHaveBeenCalledTimes(1)
26+
expect(mockSubscriptionTransport.subscribe).toHaveBeenCalledWith({
27+
topic,
28+
args,
29+
onData: expect.any(Function),
30+
onError
31+
})
32+
expect(sub).toStrictEqual({
33+
unsubscribe: expect.any(Function)
34+
})
35+
})
36+
37+
test("unsubscribe from a subscription", async () => {
38+
const topic = "topic"
39+
const args = {foo: "bar"}
40+
const onData = jest.fn()
41+
const onError = jest.fn()
42+
43+
const sub = manager.subscribe({topic, args, onData, onError})
44+
sub.unsubscribe()
45+
46+
expect(mockSubscriptionTransport.subscribe).toHaveBeenCalledTimes(1)
47+
expect(mockSubscriptionTransport.subscribe).toHaveBeenCalledWith({
48+
topic,
49+
args,
50+
onData: expect.any(Function),
51+
onError
52+
})
53+
expect(mockSubscriptionTransport.subscribe.mock.results[0].value.unsubscribe).toHaveBeenCalledTimes(1)
54+
})
55+
})
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
export type Subscription = {
2+
unsubscribe: () => void
3+
}
4+
5+
export type SubscriptionTransportCstr = new (
6+
config: SubscriptionTransportConfig
7+
) => SubscriptionTransport
8+
9+
export type SubscriptionTransport = {
10+
subscribe: ({
11+
topic,
12+
args,
13+
onData,
14+
onError,
15+
}: {
16+
topic: string
17+
args: any
18+
onData: (data: any) => void
19+
onError: (error: Error) => void
20+
}) => Subscription
21+
}
22+
23+
export type SubscriptionTransportConfig = {
24+
node: string
25+
}
26+
27+
export class SubscriptionManager {
28+
constructor(private readonly transport: SubscriptionTransport) {}
29+
30+
subscribe({
31+
topic,
32+
args,
33+
onData,
34+
onError,
35+
}: {
36+
topic: string
37+
args: any
38+
onData: (data: any) => void
39+
onError: (error: Error) => void
40+
}) {
41+
const sub = this.transport.subscribe({
42+
topic,
43+
args,
44+
onData: data => {
45+
onData(this.decode(topic, data))
46+
},
47+
onError,
48+
})
49+
50+
return sub
51+
}
52+
53+
private decode(topic: string, data: any): any {
54+
// TODO
55+
return data
56+
}
57+
}

0 commit comments

Comments
 (0)