Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/feat-websocket-js-query-params.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@asyncapi/generator": minor
---

Add query parameter support to JavaScript WebSocket client template. The template now extracts query parameters from AsyncAPI channel bindings and generates constructor parameters to automatically append them to the WebSocket URL using URLSearchParams.
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import { CloseConnection, RegisterMessageHandler, RegisterErrorHandler, SendOper
import { ModuleExport } from './ModuleExport';
import { CompileOperationSchemas } from './CompileOperationSchemas';

export function ClientClass({ clientName, serverUrl, title, sendOperations }) {
export function ClientClass({ clientName, serverUrl, title, sendOperations, query }) {
return (
<Text>
<Text newLines={2}>
{`class ${clientName} {`}
</Text>
<Constructor serverUrl={serverUrl} sendOperations={sendOperations} />
<Constructor serverUrl={serverUrl} sendOperations={sendOperations} query={query} />
<Connect language="javascript" title={title} />
<RegisterMessageHandler
language="javascript"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,46 @@
import { Text } from '@asyncapi/generator-react-sdk';

export function Constructor({ serverUrl, sendOperations }) {
export function Constructor({ serverUrl, sendOperations, query }) {
const sendOperationsId = sendOperations.map((operation) => operation.id());
const sendOperationsArray = JSON.stringify(sendOperationsId);
const queryParamsArray = query && Array.from(query.entries());

const getConstructorSignature = () => {
if (!queryParamsArray || queryParamsArray.length === 0) {
return 'constructor(url)';
}
const queryParamNames = queryParamsArray.map(([paramName]) => paramName).join(', ');
return `constructor(url, ${queryParamNames})`;
};

const getQueryParamsDocumentation = () => {
if (!queryParamsArray || queryParamsArray.length === 0) {
return '';
}
return queryParamsArray.map(([paramName]) => `\n * @param {string} ${paramName} - Query parameter for the WebSocket URL`).join('');
};

const getQueryParamsInitialization = () => {
if (!queryParamsArray || queryParamsArray.length === 0) {
return '';
}
return queryParamsArray.map(([paramName]) => `\n if (${paramName}) params['${paramName}'] = ${paramName};`).join('');
};

return (
<Text indent={2}>
{
`/*
* Constructor to initialize the WebSocket client
* @param {string} url - The WebSocket server URL. Use it if the server URL is different from the default one taken from the AsyncAPI document.
* @param {string} url - The WebSocket server URL. Use it if the server URL is different from the default one taken from the AsyncAPI document.${getQueryParamsDocumentation()}
*/
constructor(url) {
this.url = url || '${serverUrl}';
${getConstructorSignature()} {${
query ? `
const params = {};${getQueryParamsInitialization()}
const qs = new URLSearchParams(params).toString();
this.url = qs ? \`\${url || '${serverUrl}'}?\${qs}\` : (url || '${serverUrl}');` : `
this.url = url || '${serverUrl}';`
}
this.websocket = null;
this.messageHandlers = [];
this.errorHandlers = [];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { File } from '@asyncapi/generator-react-sdk';
import { getClientName, getServerUrl, getServer, getInfo, getTitle } from '@asyncapi/generator-helpers';
import { getClientName, getServerUrl, getServer, getInfo, getTitle, getQueryParams } from '@asyncapi/generator-helpers';
import { FileHeaderInfo, DependencyProvider } from '@asyncapi/generator-components';
import { ClientClass } from '../components/ClientClass';

Expand All @@ -10,6 +10,7 @@ export default function ({ asyncapi, params }) {
const clientName = getClientName(asyncapi, params.appendClientSuffix, params.customClientName);
const serverUrl = getServerUrl(server);
const sendOperations = asyncapi.operations().filterBySend();
const queryParams = getQueryParams(asyncapi.channels());
const asyncapiFilepath = `${params.asyncapiFileDir}/asyncapi.yaml`;
return (
<File name={params.clientFileName}>
Expand All @@ -22,7 +23,7 @@ export default function ({ asyncapi, params }) {
language="javascript"
additionalDependencies={['const path = require(\'path\');', `const asyncapiFilepath = path.resolve(__dirname, '${asyncapiFilepath}');`]}
/>
<ClientClass clientName={clientName} serverUrl={serverUrl} title={title} sendOperations={sendOperations} />
<ClientClass clientName={clientName} serverUrl={serverUrl} title={title} sendOperations={sendOperations} query={queryParams} />
</File>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import path from 'path';
import { render } from '@asyncapi/generator-react-sdk';
import { Parser, fromFile } from '@asyncapi/parser';
import { getServer, getServerUrl, getQueryParams } from '@asyncapi/generator-helpers';
import { Constructor } from '../../components/Constructor.js';

const parser = new Parser();
const asyncapiFilePath = path.resolve(__dirname, '../../../../../../helpers/test/__fixtures__/asyncapi-websocket-query.yml');

describe('Constructor component (integration with AsyncAPI document)', () => {
let servers;
let parsedAsyncAPIDocument;
let sendOperations;

beforeAll(async () => {
const parseResult = await fromFile(parser, asyncapiFilePath).parse();
parsedAsyncAPIDocument = parseResult.document;
servers = parsedAsyncAPIDocument.servers();
sendOperations = parsedAsyncAPIDocument.operations().filterBySend();
});

test('renders with only serverUrl (withHostDuplicatingProtocol)', () => {
const server = getServer(servers, 'withHostDuplicatingProtocol');
const serverUrl = getServerUrl(server);
const result = render(<Constructor serverUrl={serverUrl} sendOperations={sendOperations} query={null} />);
expect(result.trim()).toMatchSnapshot();
});

test('renders with neither serverUrl nor query', () => {
const result = render(<Constructor serverUrl={null} sendOperations={sendOperations} query={null} />);
expect(result.trim()).toMatchSnapshot();
});

test('renders with undefined query', () => {
const server = getServer(servers, 'withVariables');
const serverUrl = getServerUrl(server);
const result = render(<Constructor serverUrl={serverUrl} sendOperations={sendOperations} />);
expect(result.trim()).toMatchSnapshot();
});

test('renders with serverUrl and query parameters', () => {
const server = getServer(servers, 'withVariables');
const serverUrl = getServerUrl(server);
const channels = parsedAsyncAPIDocument.channels();
const queryParams = getQueryParams(channels);
const result = render(<Constructor serverUrl={serverUrl} sendOperations={sendOperations} query={queryParams} />);
expect(result.trim()).toMatchSnapshot();
});
});

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Constructor component (integration with AsyncAPI document) renders with neither serverUrl nor query 1`] = `
"/*
* Constructor to initialize the WebSocket client
* @param {string} url - The WebSocket server URL. Use it if the server URL is different from the default one taken from the AsyncAPI document.
*/
constructor(url) {
this.url = url || 'null';
this.websocket = null;
this.messageHandlers = [];
this.errorHandlers = [];
this.compiledSchemas = {};
this.schemasCompiled = false;
this.sendOperationsId = [\\"noMessage\\",\\"noSummaryOperations\\",\\"noSummaryNoDescriptionOperations\\",\\"mixedMessageExamples\\",\\"operation_with_snake_case\\"];
}"
`;

exports[`Constructor component (integration with AsyncAPI document) renders with only serverUrl (withHostDuplicatingProtocol) 1`] = `
"/*
* Constructor to initialize the WebSocket client
* @param {string} url - The WebSocket server URL. Use it if the server URL is different from the default one taken from the AsyncAPI document.
*/
constructor(url) {
this.url = url || 'wss://api.gemini.com';
this.websocket = null;
this.messageHandlers = [];
this.errorHandlers = [];
this.compiledSchemas = {};
this.schemasCompiled = false;
this.sendOperationsId = [\\"noMessage\\",\\"noSummaryOperations\\",\\"noSummaryNoDescriptionOperations\\",\\"mixedMessageExamples\\",\\"operation_with_snake_case\\"];
}"
`;

exports[`Constructor component (integration with AsyncAPI document) renders with serverUrl and query parameters 1`] = `
"/*
* Constructor to initialize the WebSocket client
* @param {string} url - The WebSocket server URL. Use it if the server URL is different from the default one taken from the AsyncAPI document.
* @param {string} heartbeat - Query parameter for the WebSocket URL
* @param {string} top_of_book - Query parameter for the WebSocket URL
* @param {string} bids - Query parameter for the WebSocket URL
* @param {string} offers - Query parameter for the WebSocket URL
*/
constructor(url, heartbeat, top_of_book, bids, offers) {
const params = {};
if (heartbeat) params['heartbeat'] = heartbeat;
if (top_of_book) params['top_of_book'] = top_of_book;
if (bids) params['bids'] = bids;
if (offers) params['offers'] = offers;
const qs = new URLSearchParams(params).toString();
this.url = qs ? \`\${url || 'wss://api.gemini.com/v1/marketdata/{symbol}'}?\${qs}\` : (url || 'wss://api.gemini.com/v1/marketdata/{symbol}');
this.websocket = null;
this.messageHandlers = [];
this.errorHandlers = [];
this.compiledSchemas = {};
this.schemasCompiled = false;
this.sendOperationsId = [\\"noMessage\\",\\"noSummaryOperations\\",\\"noSummaryNoDescriptionOperations\\",\\"mixedMessageExamples\\",\\"operation_with_snake_case\\"];
}"
`;

exports[`Constructor component (integration with AsyncAPI document) renders with undefined query 1`] = `
"/*
* Constructor to initialize the WebSocket client
* @param {string} url - The WebSocket server URL. Use it if the server URL is different from the default one taken from the AsyncAPI document.
*/
constructor(url) {
this.url = url || 'wss://api.gemini.com/v1/marketdata/{symbol}';
this.websocket = null;
this.messageHandlers = [];
this.errorHandlers = [];
this.compiledSchemas = {};
this.schemasCompiled = false;
this.sendOperationsId = [\\"noMessage\\",\\"noSummaryOperations\\",\\"noSummaryNoDescriptionOperations\\",\\"mixedMessageExamples\\",\\"operation_with_snake_case\\"];
}"
`;

This file was deleted.

Loading