Skip to content

Commit

Permalink
A channel augmenter that encodes and decodes messages as JSON (#3309)
Browse files Browse the repository at this point in the history
# Summary

The new web socket channel speaks in strings/buffers. For general use in the JSON RPC Subscriptions API we need it to take in JavaScript objects, send JSON strings over the wire, and parse the strings that come back as JSON.

This is a higher-order component that wraps a `RpcSubscriptionsChannel` that speaks in strings, and makes it speak in JavaScript objects.
  • Loading branch information
steveluscher authored Oct 4, 2024
1 parent c0d659a commit 994441a
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 1 deletion.
8 changes: 7 additions & 1 deletion packages/rpc-subscriptions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,10 @@

# @solana/rpc-subscriptions

TODO
This package contains types that implement RPC subscriptions as required by the Solana RPC. Additionally, it incorporates some useful defaults that make working with subscriptions easier, more performant, and more reliable. It can be used standalone, but it is also exported as part of the Solana JavaScript SDK [`@solana/web3.js@rc`](https://github.com/solana-labs/solana-web3.js/tree/master/packages/library).

## Functions

### `getRpcSubscriptionsChannelWithJSONSerialization(channel)`

Given an `RpcSubscriptionsChannel`, will return a new channel that parses data published to the `'message'` channel as JSON, and JSON-stringifies messages sent via the `send(message)` method.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { RpcSubscriptionsChannel } from '@solana/rpc-subscriptions-spec';

import { getRpcSubscriptionsChannelWithJSONSerialization } from '../rpc-subscriptions-json';

describe('getRpcSubscriptionsChannelWithJSONSerialization', () => {
let mockOn: jest.Mock;
let channel: RpcSubscriptionsChannel<string, string>;
function receiveMessage(message: unknown) {
mockOn.mock.calls.filter(([type]) => type === 'message').forEach(([_, listener]) => listener(message));
}
beforeEach(() => {
mockOn = jest.fn();
channel = {
on: mockOn,
send: jest.fn(),
};
});
it('forwards JSON-serialized messages to the underlying channel', () => {
const channelWithJSONSerialization = getRpcSubscriptionsChannelWithJSONSerialization(channel);
channelWithJSONSerialization.send('hello');
expect(channel.send).toHaveBeenCalledWith(JSON.stringify('hello'));
});
it('deserializes messages received from the underlying channel as JSON', () => {
const channelWithJSONSerialization = getRpcSubscriptionsChannelWithJSONSerialization(channel);
const messageListener = jest.fn();
channelWithJSONSerialization.on('message', messageListener);
receiveMessage(JSON.stringify('hello'));
expect(messageListener).toHaveBeenCalledWith('hello');
});
});
26 changes: 26 additions & 0 deletions packages/rpc-subscriptions/src/rpc-subscriptions-json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { RpcSubscriptionsChannel } from '@solana/rpc-subscriptions-spec';

export function getRpcSubscriptionsChannelWithJSONSerialization(
channel: RpcSubscriptionsChannel<string, string>,
): RpcSubscriptionsChannel<unknown, unknown> {
return Object.freeze({
...channel,
on(type, listener, options) {
if (type !== 'message') {
return channel.on(type, listener, options);
}
return channel.on(
'message',
function deserializingListener(message: string) {
const deserializedMessage = JSON.parse(message);
listener(deserializedMessage);
},
options,
);
},
send(message) {
const serializedMessage = JSON.stringify(message);
return channel.send(serializedMessage);
},
} as RpcSubscriptionsChannel<unknown, unknown>);
}

0 comments on commit 994441a

Please sign in to comment.