Skip to content

Commit fabca27

Browse files
tabaktoniivpavicipenovicp
authored
feat: the WebSockets (#1251)
* feat: ws init setup * feat: init ws and test * feat: subscribe, unsubscribe, waitForDisconnection, test flow for newHeads working fine * feat: subscribeNewHeads done and tested * feat: test standard endpoint, update wait methods with statuses * feat: transaction status and pending transactions * fix: missing file * feat: onUnsubscribe, make bulk test works * feat: beta types-js release and import, on methods data type definitions added * feat: docs, extended tests, events map, types * feat: manage subscriptions by map * test: onUnsubscribe, docs, cleanup * fix: cleanup * fix: tipo and test * Update __tests__/WebSocketChannel.test.ts Co-authored-by: Ivan Pavičić <[email protected]> * Update src/channel/ws_0_8.ts Co-authored-by: Ivan Pavičić <[email protected]> * Update src/channel/ws_0_8.ts Co-authored-by: Ivan Pavičić <[email protected]> * fix: a SUBSCRIPTION_RESULT, bump types beta.2, debug tests * chore: starknet-types to starknet-types-08 * docs: websocket channel docs init * docs: websocket channel docs init * fix: block_id and txStatus event name * feat: add types-beta-4, add T check on socket methods * test: stabilize websocket tests (#1272) * feat: onReorg * fix: update packages * chore: updated json lock --------- Co-authored-by: Ivan Pavičić <[email protected]> Co-authored-by: Petar Penović <[email protected]>
1 parent 00743de commit fabca27

File tree

10 files changed

+1744
-1970
lines changed

10 files changed

+1744
-1970
lines changed

__tests__/WebSocketChannel.test.ts

+198
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
import { WebSocket } from 'isows';
2+
3+
import { Provider, WSSubscriptions, WebSocketChannel } from '../src';
4+
import { StarknetChainId } from '../src/constants';
5+
import { getTestAccount, getTestProvider } from './config/fixtures';
6+
7+
const nodeUrl = 'wss://sepolia-pathfinder-rpc.spaceshard.io/rpc/v0_8';
8+
9+
describe('websocket specific endpoints - pathfinder test', () => {
10+
// account provider
11+
const provider = new Provider(getTestProvider());
12+
const account = getTestAccount(provider);
13+
14+
// websocket
15+
let webSocketChannel: WebSocketChannel;
16+
17+
beforeAll(async () => {
18+
webSocketChannel = new WebSocketChannel({ nodeUrl });
19+
expect(webSocketChannel.isConnected()).toBe(false);
20+
try {
21+
await webSocketChannel.waitForConnection();
22+
} catch (error: any) {
23+
console.log(error.message);
24+
}
25+
expect(webSocketChannel.isConnected()).toBe(true);
26+
});
27+
28+
afterAll(async () => {
29+
expect(webSocketChannel.isConnected()).toBe(true);
30+
webSocketChannel.disconnect();
31+
await expect(webSocketChannel.waitForDisconnection()).resolves.toBe(WebSocket.CLOSED);
32+
});
33+
34+
test('Test WS Error and edge cases', async () => {
35+
webSocketChannel.disconnect();
36+
37+
// should fail as disconnected
38+
await expect(webSocketChannel.subscribeNewHeads()).rejects.toThrow();
39+
40+
// should reconnect
41+
webSocketChannel.reconnect();
42+
await webSocketChannel.waitForConnection();
43+
44+
// should succeed after reconnection
45+
await expect(webSocketChannel.subscribeNewHeads()).resolves.toEqual(expect.any(Number));
46+
47+
// should fail because already subscribed
48+
await expect(webSocketChannel.subscribeNewHeads()).resolves.toBe(false);
49+
});
50+
51+
test('onUnsubscribe with unsubscribeNewHeads', async () => {
52+
const mockOnUnsubscribe = jest.fn().mockImplementation((subId: number) => {
53+
expect(subId).toEqual(expect.any(Number));
54+
});
55+
webSocketChannel.onUnsubscribe = mockOnUnsubscribe;
56+
57+
await webSocketChannel.subscribeNewHeads();
58+
await expect(webSocketChannel.unsubscribeNewHeads()).resolves.toBe(true);
59+
await expect(webSocketChannel.unsubscribeNewHeads()).rejects.toThrow();
60+
61+
expect(mockOnUnsubscribe).toHaveBeenCalled();
62+
expect(webSocketChannel.subscriptions.has(WSSubscriptions.NEW_HEADS)).toBeFalsy();
63+
});
64+
65+
test('Test subscribeNewHeads', async () => {
66+
await webSocketChannel.subscribeNewHeads();
67+
68+
let i = 0;
69+
webSocketChannel.onNewHeads = async function (data) {
70+
expect(this).toBeInstanceOf(WebSocketChannel);
71+
i += 1;
72+
// TODO : Add data format validation
73+
expect(data.result).toBeDefined();
74+
if (i === 2) {
75+
const status = await webSocketChannel.unsubscribeNewHeads();
76+
expect(status).toBe(true);
77+
}
78+
};
79+
const expectedId = webSocketChannel.subscriptions.get(WSSubscriptions.NEW_HEADS);
80+
const subscriptionId = await webSocketChannel.waitForUnsubscription(expectedId);
81+
expect(subscriptionId).toBe(expectedId);
82+
expect(webSocketChannel.subscriptions.get(WSSubscriptions.NEW_HEADS)).toBe(undefined);
83+
});
84+
85+
test('Test subscribeEvents', async () => {
86+
await webSocketChannel.subscribeEvents();
87+
88+
let i = 0;
89+
webSocketChannel.onEvents = async (data) => {
90+
i += 1;
91+
// TODO : Add data format validation
92+
expect(data.result).toBeDefined();
93+
if (i === 5) {
94+
const status = await webSocketChannel.unsubscribeEvents();
95+
expect(status).toBe(true);
96+
}
97+
};
98+
const expectedId = webSocketChannel.subscriptions.get(WSSubscriptions.EVENTS);
99+
const subscriptionId = await webSocketChannel.waitForUnsubscription(expectedId);
100+
expect(subscriptionId).toBe(expectedId);
101+
expect(webSocketChannel.subscriptions.get(WSSubscriptions.EVENTS)).toBe(undefined);
102+
});
103+
104+
test('Test subscribePendingTransaction', async () => {
105+
await webSocketChannel.subscribePendingTransaction(true);
106+
107+
let i = 0;
108+
webSocketChannel.onPendingTransaction = async (data) => {
109+
i += 1;
110+
// TODO : Add data format validation
111+
expect(data.result).toBeDefined();
112+
if (i === 5) {
113+
const status = await webSocketChannel.unsubscribePendingTransaction();
114+
expect(status).toBe(true);
115+
}
116+
};
117+
const expectedId = webSocketChannel.subscriptions.get(WSSubscriptions.PENDING_TRANSACTION);
118+
const subscriptionId = await webSocketChannel.waitForUnsubscription(expectedId);
119+
expect(subscriptionId).toBe(expectedId);
120+
expect(webSocketChannel.subscriptions.get(WSSubscriptions.PENDING_TRANSACTION)).toBe(undefined);
121+
});
122+
123+
test('Test subscribeTransactionStatus', async () => {
124+
const { transaction_hash } = await account.execute({
125+
contractAddress: '0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d',
126+
entrypoint: 'transfer',
127+
calldata: [account.address, '10', '0'],
128+
});
129+
130+
let i = 0;
131+
webSocketChannel.onTransactionStatus = async (data) => {
132+
i += 1;
133+
// TODO : Add data format validation
134+
expect(data.result).toBeDefined();
135+
if (i >= 1) {
136+
const status = await webSocketChannel.unsubscribeTransactionStatus();
137+
expect(status).toBe(true);
138+
}
139+
};
140+
141+
const subid = await webSocketChannel.subscribeTransactionStatus(transaction_hash);
142+
expect(subid).toEqual(expect.any(Number));
143+
const expectedId = webSocketChannel.subscriptions.get(WSSubscriptions.TRANSACTION_STATUS);
144+
const subscriptionId = await webSocketChannel.waitForUnsubscription(expectedId);
145+
expect(subscriptionId).toEqual(expectedId);
146+
expect(webSocketChannel.subscriptions.get(WSSubscriptions.TRANSACTION_STATUS)).toBe(undefined);
147+
});
148+
149+
test('Test subscribeTransactionStatus and block_id', async () => {
150+
const latestBlock = await account.getBlockLatestAccepted();
151+
const blockId = latestBlock.block_number - 5;
152+
153+
const { transaction_hash } = await account.execute({
154+
contractAddress: '0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d',
155+
entrypoint: 'transfer',
156+
calldata: [account.address, '10', '0'],
157+
});
158+
159+
let i = 0;
160+
webSocketChannel.onTransactionStatus = async (data) => {
161+
i += 1;
162+
// TODO : Add data format validation
163+
expect(data.result).toBeDefined();
164+
if (i >= 1) {
165+
const status = await webSocketChannel.unsubscribeTransactionStatus();
166+
expect(status).toBe(true);
167+
}
168+
};
169+
170+
const subid = await webSocketChannel.subscribeTransactionStatus(transaction_hash, blockId);
171+
expect(subid).toEqual(expect.any(Number));
172+
const expectedId = webSocketChannel.subscriptions.get(WSSubscriptions.TRANSACTION_STATUS);
173+
const subscriptionId = await webSocketChannel.waitForUnsubscription(expectedId);
174+
expect(subscriptionId).toEqual(expectedId);
175+
expect(webSocketChannel.subscriptions.get(WSSubscriptions.TRANSACTION_STATUS)).toBe(undefined);
176+
});
177+
});
178+
179+
describe('websocket regular endpoints - pathfinder test', () => {
180+
let webSocketChannel: WebSocketChannel;
181+
182+
beforeAll(async () => {
183+
webSocketChannel = new WebSocketChannel({ nodeUrl });
184+
expect(webSocketChannel.isConnected()).toBe(false);
185+
const status = await webSocketChannel.waitForConnection();
186+
expect(status).toBe(WebSocket.OPEN);
187+
});
188+
189+
afterAll(async () => {
190+
expect(webSocketChannel.isConnected()).toBe(true);
191+
webSocketChannel.disconnect();
192+
});
193+
194+
test('regular rpc endpoint', async () => {
195+
const response = await webSocketChannel.sendReceiveAny('starknet_chainId');
196+
expect(response).toBe(StarknetChainId.SN_SEPOLIA);
197+
});
198+
});

__tests__/utils/stark.browser.test.ts

+5
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,14 @@ import * as json from '../../src/utils/json';
88

99
const { IS_BROWSER } = constants;
1010

11+
// isows has faulty module resolution in the browser emulation environment which prevents test execution
12+
// it is not required for these tests so removing it with a mock circumvents the issue
13+
jest.mock('isows', () => jest.fn());
14+
1115
test('isBrowser', () => {
1216
expect(IS_BROWSER).toBe(true);
1317
});
18+
1419
describe('compressProgram()', () => {
1520
// the @noble/curves dependency that is included in this file through utils/stark executes precomputations
1621
// that rely on TextEncoder. The jsdom environment does not expose TextEncoder and it also overrides

0 commit comments

Comments
 (0)