diff --git a/packages/gleequore/index.d.ts b/packages/gleequore/index.d.ts deleted file mode 100644 index e6918d588..000000000 --- a/packages/gleequore/index.d.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './src' \ No newline at end of file diff --git a/packages/gleequore/package.json b/packages/gleequore/package.json index 0e285270b..11781b1b0 100644 --- a/packages/gleequore/package.json +++ b/packages/gleequore/package.json @@ -2,12 +2,16 @@ "name": "@asyncapi/gleequore", "version": "0.0.0", "description": "The Quore of Glee", - "exports": "./dist/moduleIndex.js", + "main": "./dist/index.js", "type": "module", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, - "types": "./index.d.ts", + "files": [ + "dist", + "./src/index.d.ts" + ], + "types": "./src/index.d.ts", "scripts": { "build": "tsc", "bump:version": "npm --no-git-tag-version --allow-same-version version $VERSION", @@ -32,21 +36,13 @@ }, "license": "Apache-2.0", "dependencies": { + "@asyncapi/glee-shared-utils": "workspace:*", "@asyncapi/parser": "^3.1.0", - "ajv": "^6.12.6", "async": "^3.2.0", - "better-ajv-errors": "^0.7.0", "debug": "^4.3.1", - "got": "^12.5.3", - "kafkajs": "^2.2.3", - "mqtt": "^4.3.7", - "path-to-regexp": "^6.2.0", - "redis": "^4.0.2", - "socket.io": "^4.1.2", "typescript": "^4.5.4", "uri-templates": "^0.2.0", - "uuid": "^8.3.2", - "ws": "^7.4.6" + "uuid": "^8.3.2" }, "devDependencies": { "@jest/globals": "29.7.0", @@ -54,9 +50,7 @@ "@types/async": "^3.2.11", "@types/debug": "^4.1.7", "@types/jest": "^29.5.14", - "@types/socket.io": "^3.0.2", "@types/uri-templates": "^0.1.31", - "@types/ws": "^8.5.3", "@typescript-eslint/eslint-plugin": "^5.9.0", "@typescript-eslint/parser": "^5.9.0", "eslint": "^8.6.0", diff --git a/packages/gleequore/src/index.d.ts b/packages/gleequore/src/index.d.ts index 04eee3fba..808ab4fc1 100644 --- a/packages/gleequore/src/index.d.ts +++ b/packages/gleequore/src/index.d.ts @@ -5,42 +5,17 @@ import GleeQuoreConnection from './lib/connection.js' import GleeQuore from './index.ts' import type GleeQuoreMessage from './lib/message.js' -type WebSocketServerType = 'native' | 'socket.io' -type HttpServerType = 'native' -type QueryParam = { [key: string]: string } | { [key: string]: string[] } - -export type AuthFunction = ({ - serverName, - parsedAsyncAPI, -}: { - serverName: string - parsedAsyncAPI: AsyncAPIDocument -}) => Promise +export { default as GleeQuoreAdapter } from './lib/adapter.js' +export { default as GleeQuoreMessage } from './lib/message.js' +export { default as GleeQuoreConnection } from './lib/connection.js' +export { default as GleeQuoreClusterAdapter } from './lib/cluster.js' +export { default as GleeQuoreError } from './errors.js' export interface AuthFunctionInfo { clientAuth?: GleeQuoreAuthFunction serverAuth?: GleeQuoreAuthFunction } -export interface MqttAuthConfig { - cert?: string - username?: string - password?: string - clientId?: string -} - -export interface WsAuthConfig { - token?: string - username?: string - password?: string -} - -export interface HttpAuthConfig { - token?: string - username?: string - password?: string -} - export type AuthProps = { getToken: () => string getUserPass: () => { @@ -53,77 +28,12 @@ export type AuthProps = { getAPIKeys: () => string } -export type WsHttpAuth = WsAuthConfig | HttpAuthConfig - -export interface KafkaAuthConfig { - key?: string - cert?: string - clientId?: string - rejectUnauthorized?: boolean - username?: string - password?: string -} - export type GleeQuoreClusterAdapterConfig = { adapter?: string | typeof GleeQuoreClusterAdapter name?: string url: string } -export type WebsocketServerAdapterConfig = { - httpServer?: any - adapter?: WebSocketServerType | typeof GleeQuoreAdapter - port?: number -} - -export type WebsocketAdapterConfig = { - server?: WebsocketServerAdapterConfig -} - -export type HttpAdapterConfig = { - server: { - httpServer?: any - port?: number - } - client?: { - auth?: HttpAuthConfig | AuthFunction - query?: QueryParam - body?: any - } -} -export type MqttAdapterConfig = { - auth?: MqttAuthConfig | AuthFunction -} - -export type KafkaAdapterConfig = { - auth?: KafkaAuthConfig | AuthFunction -} - -export enum Log { - CHANNEL_ONLY = 'channel-only', - NONE = 'none' -} - -export type LogsConfig = { - incoming: Log - outgoing: Log -} - -export interface GleeQuoreConfig { - includeServers?: string[], - cluster?: GleeQuoreClusterAdapterConfig, - http?: HttpAdapterConfig, - kafka?: KafkaAdapterConfig, - ws?: WebsocketAdapterConfig, - mqtt?: MqttAdapterConfig, -} - -export type GleeQuoreFunctionReturn = { - send?: GleeQuoreFunctionReturnSend[] - reply?: GleeQuoreFunctionReturnReply[] - broadcast?: GleeQuoreFunctionReturnBroadcast[] -} - export type GleeQuoreFunctionEvent = { request: GleeQuoreMessage app: GleeQuore @@ -145,17 +55,6 @@ export type GleeQuoreAuthFunctionEvent = { doc: any } -export type GleeQuoreFunctionReturnSend = { - payload?: any - query?: QueryParam - headers?: { [key: string]: string } - channel?: string - server?: string -} - -export type GleeQuoreFunctionReturnReply = Omit -export type GleeQuoreFunctionReturnBroadcast = GleeQuoreFunctionReturnSend - export type GleeQuoreFunction = ( event: GleeQuoreFunctionEvent ) => Promise @@ -168,11 +67,12 @@ export type GleeQuoreAuthFunction = ( event: GleeQuoreAuthFunctionEvent ) => Promise | void -type Headers = { [key: string]: string } -export interface Authenticatable { - headers: Headers, - query: QueryParam - url: URL +export interface GleeQuoreAdapterOptions { + glee: GleeQuore; + serverName: string; + server: ServerInterface; + parsedAsyncAPI: AsyncAPIDocument; + config?: object } export type AdapterRecord = { @@ -181,9 +81,12 @@ export type AdapterRecord = { serverName: string server: ServerInterface asyncapi: AsyncAPIDocumentInterface + config?: object } export type ClusterAdapterRecord = { Adapter: typeof GleeQuoreClusterAdapter - instance?: GleeQuoreClusterAdapter + instance?: GleeQuoreClusterAdapter, + clusterName?: string, + clusterURL?: string } diff --git a/packages/gleequore/src/index.ts b/packages/gleequore/src/index.ts index e3debb7ca..2c09f6ca8 100644 --- a/packages/gleequore/src/index.ts +++ b/packages/gleequore/src/index.ts @@ -2,7 +2,7 @@ import EventEmitter from 'events' import async from 'async' import Debug from 'debug' -import GleeQuoreAdapter, { GleeQuoreAdapterOptions } from './lib/adapter.js' +import GleeQuoreAdapter from './lib/adapter.js' import GleeQuoreClusterAdapter from './lib/cluster.js' import GleeQuoreRouter, { ChannelErrorMiddlewareTuple, @@ -10,7 +10,8 @@ import GleeQuoreRouter, { GenericMiddleware, } from './lib/router.js' import GleeQuoreMessage, { IGleeQuoreMessageConstructor } from './lib/message.js' -import { matchChannel, duplicateMessage, getParams } from './lib/util.js' +import { matchChannel, getParams } from '@asyncapi/glee-shared-utils' +import { duplicateMessage } from './lib/utils.js' import GleeQuoreConnection from './lib/connection.js' import { MiddlewareCallback } from './middlewares/index.js' import buffer2string from './middlewares/buffer2string.js' @@ -19,19 +20,11 @@ import json2string from './middlewares/json2string.js' import validate from './middlewares/validate.js' import existsInAsyncAPI from './middlewares/existsInAsyncAPI.js' import validateConnection from './middlewares/validateConnection.js' -import WebSocketServerAdapter from './adapters/ws/server.js' -import WebsocketClientAdapter from './adapters/ws/client.js' import { EnrichedEvent, AuthEvent } from './lib/adapter.js' -import { getMessagesSchema } from './lib/util.js' +import { getMessagesSchema } from '@asyncapi/glee-shared-utils' -import { AsyncAPIDocumentInterface, ServerInterface } from '@asyncapi/parser' -import { AdapterRecord, AuthFunctionInfo, ClusterAdapterRecord, GleeQuoreFunction, GleeQuoreLifecycleFunction, GleeQuoreConfig, GleeQuoreLifecycleEvent, GleeQuoreFunctionEvent, GleeQuoreAuthFunction, GleeQuoreAuthFunctionEvent } from './index.d.js' -import MqttAdapter from './adapters/mqtt/index.js' -import KafkaAdapter from './adapters/kafka/index.js' -import HttpClientAdapter from './adapters/http/client.js' -import HttpServerAdapter from './adapters/http/server.js' -import RedisClusterAdapter from './adapters/cluster/redis/index.js' -import SocketIOAdapter from './adapters/socket.io/index.js' +import { AsyncAPIDocumentInterface } from '@asyncapi/parser' +import { AdapterRecord, AuthFunctionInfo, ClusterAdapterRecord, GleeQuoreFunction, GleeQuoreLifecycleFunction, GleeQuoreLifecycleEvent, GleeQuoreFunctionEvent, GleeQuoreAuthFunction, GleeQuoreAuthFunctionEvent, GleeQuoreAdapterOptions } from './index.d.js' const debug = Debug('gleequore') @@ -55,7 +48,6 @@ export interface FunctionInfoRecord { export default class GleeQuore { private _asyncapi: AsyncAPIDocumentInterface - private _options: GleeQuoreConfig private _router: GleeQuoreRouter private _adapters: AdapterRecord[] private _clusterAdapter: ClusterAdapterRecord @@ -70,9 +62,8 @@ export default class GleeQuore { * * @param {Object} [options={}] */ - constructor(asyncapi: AsyncAPIDocumentInterface, options: GleeQuoreConfig = {}) { + constructor(asyncapi: AsyncAPIDocumentInterface) { this._asyncapi = asyncapi - this._options = options this._router = new GleeQuoreRouter() this._adapters = [] this._internalEvents = new EventEmitter({ captureRejections: true }) @@ -191,10 +182,6 @@ export default class GleeQuore { return this._asyncapi } - get options(): GleeQuoreConfig { - return this._options - } - get adapters(): AdapterRecord[] { return this._adapters } @@ -208,28 +195,30 @@ export default class GleeQuore { * * @param {GleeQuoreAdapter} adapter The adapter. * @param {String} serverName The name of the AsyncAPI Server to use with the adapter. - * @param {AsyncAPIServer} server AsyncAPI Server to use with the adapter. - * @param {AsyncAPIDocument} asyncapi The AsyncAPI document. + * @param {Object} [config] The configuration for the adapter. */ - addAdapter( - Adapter: typeof GleeQuoreAdapter, - { + addAdapter(Adapter: typeof GleeQuoreAdapter, serverName: string, config?: object) { + this._adapters.push({ + Adapter, serverName, - server, - asyncapi, - }: { serverName: string; server: ServerInterface | undefined; asyncapi: AsyncAPIDocumentInterface } - ) { - this._adapters.push({ Adapter, serverName, server, asyncapi }) + server: this.asyncapi.servers().get(serverName), + asyncapi: this.asyncapi, + config, + }) } /** * Sets the cluster adapter to use. * * @param {GleeQuoreClusterAdapter} adapter The adapter. + * @param {String} [clusterName] The name of the cluster. + * @param {String} [clusterURL] The URL of the cluster. */ - setClusterAdapter(Adapter: typeof GleeQuoreClusterAdapter) { + setClusterAdapter(Adapter: typeof GleeQuoreClusterAdapter, clusterName: string = 'cluster', clusterURL: string) { this._clusterAdapter = { Adapter, + clusterName, + clusterURL, } } @@ -290,14 +279,13 @@ export default class GleeQuore { async start(): Promise { const promises = [] - await this.registerAdapters() - this._adapters.forEach((a) => { const adapterOptions: GleeQuoreAdapterOptions = { glee: this, serverName: a.serverName, server: a.server, - parsedAsyncAPI: a.asyncapi + parsedAsyncAPI: a.asyncapi, + config: a.config, } a.instance = new a.Adapter(adapterOptions) @@ -651,108 +639,8 @@ export default class GleeQuore { this._internalEvents.on('error', errorCallback) } - private async registerAdapters() { - const serverNames = await this.getSelectedServerNames() - - serverNames.forEach((serverName) => { - const server: ServerInterface = this.asyncapi.servers().get(serverName) - - if (!server) { - throw new Error( - `Server "${serverName}" is not defined in the AsyncAPI file.` - ) - } - - const protocol = server.protocol() - const remoteServers = this.asyncapi.extensions().get('x-remoteServers')?.value() - if (['mqtt', 'mqtts', 'secure-mqtt'].includes(protocol)) { - this.addAdapter(MqttAdapter, { - serverName, - server, - asyncapi: this.asyncapi, - }) - } else if (['kafka', 'kafka-secure'].includes(protocol)) { - this.addAdapter(KafkaAdapter, { - serverName, - server, - asyncapi: this.asyncapi, - }) - } else if (['amqp', 'amqps'].includes(protocol)) { - // TODO: Implement AMQP support - } else if (['ws', 'wss'].includes(protocol)) { - const configWsAdapter = this.options?.ws?.server?.adapter - if (remoteServers && remoteServers.includes(serverName)) { - this.addAdapter(WebsocketClientAdapter, { - serverName, - server, - asyncapi: this.asyncapi, - }) - } else { - if (!configWsAdapter || configWsAdapter === 'native') { - this.addAdapter(WebSocketServerAdapter, { - serverName, - server, - asyncapi: this.asyncapi, - }) - } else if (configWsAdapter === 'socket.io') { - this.addAdapter(SocketIOAdapter, { - serverName, - server, - asyncapi: this.asyncapi, - }) - } else if (typeof configWsAdapter === 'object') { - this.addAdapter(configWsAdapter, { - serverName, - server, - asyncapi: this.asyncapi, - }) - } else { - throw new Error( - `Unknown value for websocket.adapter found in glee.config.js: ${this.options?.ws?.server?.adapter}. Allowed values are 'native-websocket', 'socket.io', or a reference to a custom Glee adapter.` - ) - } - } - } else if (['http', 'https'].includes(protocol)) { - if (remoteServers && remoteServers.includes(serverName)) { - this.addAdapter(HttpClientAdapter, { - serverName, - server, - asyncapi: this.asyncapi, - }) - } else { - this.addAdapter(HttpServerAdapter, { - serverName, - server, - asyncapi: this.asyncapi, - }) - } - } else { - // TODO: Improve error message with link to repo encouraging the developer to contribute. - throw new Error(`Protocol "${server.protocol()}" is not supported yet.`) - } - }) - - if (this.options.cluster) { - const { adapter } = this.options.cluster - - if (!adapter || adapter === 'redis') { - this.setClusterAdapter(RedisClusterAdapter) - } else if (typeof adapter === 'function') { - this.setClusterAdapter(adapter) - } else { - throw new Error(`Unknown value for cluster.adapter in glee.config.js`) - } - } - } - async getSelectedServerNames(): Promise { - const { includeServers: selectedServerNames } = this.options - if (!selectedServerNames || selectedServerNames.length === 0) { - return this.asyncapi.servers().all().map(e => e.id()) - } - - return this.asyncapi.servers().all().map(e => e.id()).filter((name) => { - return selectedServerNames.includes(name) - }) + const serverNames = this._adapters.map(a => a.serverName).filter(Boolean) + return [...new Set(serverNames)]; // Dedupe the array } } diff --git a/packages/gleequore/src/lib/adapter.ts b/packages/gleequore/src/lib/adapter.ts index 75f29b61a..498d31df9 100644 --- a/packages/gleequore/src/lib/adapter.ts +++ b/packages/gleequore/src/lib/adapter.ts @@ -5,8 +5,9 @@ import uriTemplates from 'uri-templates' import GleeQuoreConnection from './connection.js' import GleeQuore from '../index.js' import GleeQuoreMessage from './message.js' -import { resolveFunctions } from './util.js' -import { AuthProps } from '../index.d.js' +import { resolveFunctions, validateData } from '@asyncapi/glee-shared-utils' +import { AuthProps, GleeQuoreAdapterOptions } from '../index.d.js' +import GleeQuoreError from '../errors.js' export type EnrichedEvent = { connection?: GleeQuoreConnection @@ -21,13 +22,6 @@ export type AuthEvent = { doc: any } -export interface GleeQuoreAdapterOptions { - glee: GleeQuore; - serverName: string; - server: ServerInterface; - parsedAsyncAPI: AsyncAPIDocument; -} - class GleeQuoreAdapter extends EventEmitter { private _glee: GleeQuore private _serverName: string @@ -38,15 +32,16 @@ class GleeQuoreAdapter extends EventEmitter { private _channelAddresses: string[] private _connections: GleeQuoreConnection[] private _serverUrlExpanded: string + private _config: object - constructor({ glee, serverName, server, parsedAsyncAPI }: GleeQuoreAdapterOptions) { + constructor({ glee, serverName, server, parsedAsyncAPI, config }: GleeQuoreAdapterOptions) { super() this._glee = glee this._serverName = serverName this._AsyncAPIServer = server - + this._config = config this._parsedAsyncAPI = parsedAsyncAPI this._channelNames = this._parsedAsyncAPI.channels().all().map(e => e.id()) this._channelAddresses = this._parsedAsyncAPI.channels().all().map(c => c.address()) @@ -209,11 +204,9 @@ class GleeQuoreAdapter extends EventEmitter { return this._serverUrlExpanded } - async resolveProtocolConfig(protocol: string) { - if (!this.app.options[protocol]) return undefined - const protocolConfig = { ...this.app.options[protocol] } - if (!protocolConfig) return undefined - + async resolveConfig() { + if (!this._config) return undefined + const protocolConfig = { ...this._config } as any await resolveFunctions(protocolConfig) return protocolConfig } @@ -264,6 +257,19 @@ class GleeQuoreAdapter extends EventEmitter { ): Promise { throw new Error('Method `send` is not implemented.') } + + validate(data: any, schema: object, triggerError: boolean = false) { + const { isValid, errors, humanReadableError } = validateData(data, schema) + if (!isValid && triggerError) { + throw new GleeQuoreError({ humanReadableError, errors }) + } + + return { + isValid, + errors, + humanReadableError, + } + } } export default GleeQuoreAdapter diff --git a/packages/gleequore/src/lib/cluster.ts b/packages/gleequore/src/lib/cluster.ts index 8d99500f4..fecba40eb 100644 --- a/packages/gleequore/src/lib/cluster.ts +++ b/packages/gleequore/src/lib/cluster.ts @@ -3,7 +3,7 @@ import uriTemplates from 'uri-templates' import { v4 as uuidv4 } from 'uuid' import GleeQuore from '../index.js' import GleeQuoreMessage from './message.js' -import { validateData } from './util.js' +import { validateData } from '@asyncapi/glee-shared-utils' import GleeError from '../errors.js' export type ClusterEvent = { @@ -48,9 +48,9 @@ class GleeQuoreClusterAdapter extends EventEmitter { this._instanceId = uuidv4() this._glee = glee - const serverName = this._glee.options?.cluster?.name || 'cluster' + const serverName = this._glee.clusterAdapter.clusterName || 'cluster' this._serverName = serverName - const url = this._glee.options?.cluster?.url + const url = this._glee.clusterAdapter.clusterURL if (!url) { console.log( diff --git a/packages/gleequore/src/lib/utils.ts b/packages/gleequore/src/lib/utils.ts new file mode 100644 index 000000000..daa6dae4a --- /dev/null +++ b/packages/gleequore/src/lib/utils.ts @@ -0,0 +1,31 @@ +import GleeQuoreMessage from "./message.js" + +/** + * Duplicates a GleeQuoreMessage. + * + * @private + * @param {GleeQuoreMessage} message The message to duplicate. + * @return {GleeQuoreMessage} + */ +export const duplicateMessage = (message: GleeQuoreMessage): GleeQuoreMessage => { + const newMessage = new GleeQuoreMessage({ + operation: message.operation, + payload: message.payload, + headers: message.headers, + channel: message.channel, + request: message.request, + serverName: message.serverName, + connection: message.connection, + broadcast: message.broadcast, + cluster: message.cluster, + query: message.query, + }) + + if (message.isInbound()) { + newMessage.setInbound() + } else { + newMessage.setOutbound() + } + + return newMessage +} diff --git a/packages/gleequore/src/middlewares/validate.ts b/packages/gleequore/src/middlewares/validate.ts index b3fa4f545..fa8da983c 100644 --- a/packages/gleequore/src/middlewares/validate.ts +++ b/packages/gleequore/src/middlewares/validate.ts @@ -1,7 +1,7 @@ import { AsyncAPISchema } from '@asyncapi/parser' import GleeError from '../errors.js' import GleeQuoreMessage from '../lib/message.js' -import { validateData } from '../lib/util.js' +import { validateData } from '@asyncapi/glee-shared-utils' import { MiddlewareCallback } from './index.js' export default (schema: AsyncAPISchema, contextErrorMessage = '') => diff --git a/packages/gleequore/src/moduleIndex.ts b/packages/gleequore/src/moduleIndex.ts deleted file mode 100644 index 389b34172..000000000 --- a/packages/gleequore/src/moduleIndex.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default as GleeQuore } from './index.js' -export { default as GleeQuoreMessage } from './lib/message.js' diff --git a/packages/gleequore/try.js b/packages/gleequore/try.js index c95c35190..f9373b2c9 100644 --- a/packages/gleequore/try.js +++ b/packages/gleequore/try.js @@ -39,9 +39,13 @@ const { document: asyncapi } = await parser.parse({ }, }) -const gleeQuore = new GleeQuore(asyncapi, { - servers: ['test'] -}) +const gleeQuore = new GleeQuore(asyncapi) + +// gleeQuore.addAdapter(HttpAdapter, 'test', { +// httpServer: { +// port: 3000, +// }, +// }) gleeQuore.on('receiveTest', (event) => { console.log(event.payload) diff --git a/packages/gleequore/tsconfig.json b/packages/gleequore/tsconfig.json index 63f8a793f..9e5eb69c8 100644 --- a/packages/gleequore/tsconfig.json +++ b/packages/gleequore/tsconfig.json @@ -6,7 +6,8 @@ "target": "es6", "esModuleInterop": true, "moduleResolution": "nodenext", - "module": "NodeNext" + "module": "NodeNext", + "rootDir": "./src" }, "include": [ "./src/**/*" diff --git a/packages/kafka-adapter/README.md b/packages/kafka-adapter/README.md new file mode 100644 index 000000000..3ac54fec4 --- /dev/null +++ b/packages/kafka-adapter/README.md @@ -0,0 +1,3 @@ +# kafka-adapter + +Kafka adapter for Glee. diff --git a/packages/kafka-adapter/index.d.ts b/packages/kafka-adapter/index.d.ts new file mode 100644 index 000000000..feb4605e2 --- /dev/null +++ b/packages/kafka-adapter/index.d.ts @@ -0,0 +1,14 @@ +import type { AuthFunction } from "@asyncapi/gleequore" + +export interface KafkaAuthConfig { + key?: string + cert?: string + clientId?: string + rejectUnauthorized?: boolean + username?: string + password?: string +} + +export type KafkaAdapterConfig = { + auth?: KafkaAuthConfig | AuthFunction +} diff --git a/packages/gleequore/src/adapters/kafka/index.ts b/packages/kafka-adapter/index.ts similarity index 92% rename from packages/gleequore/src/adapters/kafka/index.ts rename to packages/kafka-adapter/index.ts index 0e8bfa68f..c7688a4ac 100644 --- a/packages/gleequore/src/adapters/kafka/index.ts +++ b/packages/kafka-adapter/index.ts @@ -1,9 +1,8 @@ import { Kafka, SASLOptions } from 'kafkajs' -import Adapter from '../../lib/adapter.js' -import GleeQuoreMessage from '../../lib/message.js' -import { KafkaAdapterConfig, KafkaAuthConfig } from '../../index.d.js' +import { GleeQuoreAdapter, GleeQuoreMessage } from '@asyncapi/gleequore' +import { KafkaAdapterConfig, KafkaAuthConfig } from './index.d.js' -class KafkaAdapter extends Adapter { +class KafkaAdapter extends GleeQuoreAdapter { private kafka: Kafka private firstConnect = true name(): string { @@ -15,9 +14,7 @@ class KafkaAdapter extends Adapter { } async _connect() { - const kafkaOptions: KafkaAdapterConfig = await this.resolveProtocolConfig( - 'kafka' - ) + const kafkaOptions: KafkaAdapterConfig = await this.resolveConfig() const auth: KafkaAuthConfig = await this.getAuthConfig(kafkaOptions?.auth) const securityRequirements = this.AsyncAPIServer.security().map( (sec) => { diff --git a/packages/kafka-adapter/package.json b/packages/kafka-adapter/package.json new file mode 100644 index 000000000..0164e2c3d --- /dev/null +++ b/packages/kafka-adapter/package.json @@ -0,0 +1,43 @@ +{ + "name": "@asyncapi/glee-kafka-adapter", + "version": "0.1.0", + "description": "Kafka adapter for Glee.", + "exports": "./dist/index.js", + "type": "module", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "scripts": { + "build": "tsc", + "bump:version": "npm --no-git-tag-version --allow-same-version version $VERSION", + "dev": "tsc --watch", + "docs": "typedoc --readme none --githubPages false --out docs/reference --entryPointStrategy expand ./src", + "get:version": "echo $npm_package_version", + "get:name": "echo $npm_package_name", + "generate:assets": "npm run docs", + "lint": "eslint --max-warnings 1 --config .eslintrc .", + "lint:fix": "eslint --max-warnings 1 --config .eslintrc . --fix", + "prepublishOnly": "npm run build", + "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", + "test:dev": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch" + }, + "dependencies": { + "@asyncapi/gleequore": "workspace:*", + "@asyncapi/glee-shared-utils": "workspace:*", + "kafkajs": "^2.2.3" + }, + "devDependencies": { + "@tsconfig/node14": "^1.0.1", + "@types/debug": "^4.1.7", + "@typescript-eslint/eslint-plugin": "^5.9.0", + "@typescript-eslint/parser": "^5.9.0", + "eslint": "^8.6.0", + "eslint-plugin-github": "^4.3.5", + "eslint-plugin-security": "^1.4.0", + "eslint-plugin-sonarjs": "^0.19.0", + "typedoc": "^0.26.10", + "typedoc-plugin-markdown": "^4.2.9", + "typescript": "^4.5.4" + }, + "license": "Apache-2.0" +} diff --git a/packages/kafka-adapter/tsconfig.json b/packages/kafka-adapter/tsconfig.json new file mode 100644 index 000000000..28a356c9d --- /dev/null +++ b/packages/kafka-adapter/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "skipLibCheck": true, + "outDir": "./dist", + "allowJs": false, + "target": "es6", + "esModuleInterop": true, + "moduleResolution": "nodenext", + "module": "NodeNext" + }, + "include": [ + "./*.ts" + ], + "exclude": [ + "node_modules" + ] +} \ No newline at end of file diff --git a/packages/mqtt-adapter/README.md b/packages/mqtt-adapter/README.md new file mode 100644 index 000000000..3c44f0f1a --- /dev/null +++ b/packages/mqtt-adapter/README.md @@ -0,0 +1,3 @@ +# mqtt-adapter + +MQTT adapter for Glee. diff --git a/packages/mqtt-adapter/index.d.ts b/packages/mqtt-adapter/index.d.ts new file mode 100644 index 000000000..5b656b9f9 --- /dev/null +++ b/packages/mqtt-adapter/index.d.ts @@ -0,0 +1,12 @@ +import type { AuthFunction } from "@asyncapi/gleequore" + +export interface MqttAuthConfig { + cert?: string + username?: string + password?: string + clientId?: string +} + +export type MqttAdapterConfig = { + auth?: MqttAuthConfig | AuthFunction +} diff --git a/packages/gleequore/src/adapters/mqtt/index.ts b/packages/mqtt-adapter/index.ts similarity index 96% rename from packages/gleequore/src/adapters/mqtt/index.ts rename to packages/mqtt-adapter/index.ts index 8c39ffe54..081326432 100644 --- a/packages/gleequore/src/adapters/mqtt/index.ts +++ b/packages/mqtt-adapter/index.ts @@ -1,7 +1,6 @@ import mqtt, { IPublishPacket, MqttClient, QoS } from 'mqtt' -import Adapter from '../../lib/adapter.js' -import GleeQuoreMessage from '../../lib/message.js' -import { MqttAuthConfig, MqttAdapterConfig } from '../../index.d.js' +import { GleeQuoreAdapter, GleeQuoreMessage } from '@asyncapi/gleequore' +import { MqttAuthConfig, MqttAdapterConfig } from './index.d.js' import { SecuritySchemesInterface as SecurityScheme } from '@asyncapi/parser' interface IMQTTHeaders { @@ -27,7 +26,7 @@ enum SecurityTypes { USER_PASSWORD = 'userpassword', X509 = 'x509', } -class MqttAdapter extends Adapter { +class MqttAdapter extends GleeQuoreAdapter { private client: MqttClient private firstConnect: boolean @@ -177,9 +176,7 @@ class MqttAdapter extends Adapter { } async _connect(): Promise { - const mqttOptions: MqttAdapterConfig = await this.resolveProtocolConfig( - 'mqtt' - ) + const mqttOptions: MqttAdapterConfig = await this.resolveConfig() const auth: MqttAuthConfig = await this.getAuthConfig(mqttOptions?.auth) const subscribedChannels = this.getSubscribedChannels() const mqttServerBinding = this.AsyncAPIServer.bindings().get('mqtt') diff --git a/packages/mqtt-adapter/package.json b/packages/mqtt-adapter/package.json new file mode 100644 index 000000000..5faca0500 --- /dev/null +++ b/packages/mqtt-adapter/package.json @@ -0,0 +1,43 @@ +{ + "name": "@asyncapi/glee-mqtt-adapter", + "version": "0.1.0", + "description": "MQTT adapter for Glee.", + "exports": "./dist/index.js", + "type": "module", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "scripts": { + "build": "tsc", + "bump:version": "npm --no-git-tag-version --allow-same-version version $VERSION", + "dev": "tsc --watch", + "docs": "typedoc --readme none --githubPages false --out docs/reference --entryPointStrategy expand ./src", + "get:version": "echo $npm_package_version", + "get:name": "echo $npm_package_name", + "generate:assets": "npm run docs", + "lint": "eslint --max-warnings 1 --config .eslintrc .", + "lint:fix": "eslint --max-warnings 1 --config .eslintrc . --fix", + "prepublishOnly": "npm run build", + "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", + "test:dev": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch" + }, + "dependencies": { + "@asyncapi/gleequore": "workspace:*", + "@asyncapi/glee-shared-utils": "workspace:*", + "mqtt": "^4.3.7" + }, + "devDependencies": { + "@tsconfig/node14": "^1.0.1", + "@types/debug": "^4.1.7", + "@typescript-eslint/eslint-plugin": "^5.9.0", + "@typescript-eslint/parser": "^5.9.0", + "eslint": "^8.6.0", + "eslint-plugin-github": "^4.3.5", + "eslint-plugin-security": "^1.4.0", + "eslint-plugin-sonarjs": "^0.19.0", + "typedoc": "^0.26.10", + "typedoc-plugin-markdown": "^4.2.9", + "typescript": "^4.5.4" + }, + "license": "Apache-2.0" +} diff --git a/packages/mqtt-adapter/tsconfig.json b/packages/mqtt-adapter/tsconfig.json new file mode 100644 index 000000000..28a356c9d --- /dev/null +++ b/packages/mqtt-adapter/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "skipLibCheck": true, + "outDir": "./dist", + "allowJs": false, + "target": "es6", + "esModuleInterop": true, + "moduleResolution": "nodenext", + "module": "NodeNext" + }, + "include": [ + "./*.ts" + ], + "exclude": [ + "node_modules" + ] +} \ No newline at end of file diff --git a/packages/redis-cluster-adapter/README.md b/packages/redis-cluster-adapter/README.md new file mode 100644 index 000000000..201760d74 --- /dev/null +++ b/packages/redis-cluster-adapter/README.md @@ -0,0 +1,3 @@ +# redis-cluster-adapter + +Redis Cluster adapter for Glee. diff --git a/packages/gleequore/src/adapters/cluster/redis/index.ts b/packages/redis-cluster-adapter/index.ts similarity index 91% rename from packages/gleequore/src/adapters/cluster/redis/index.ts rename to packages/redis-cluster-adapter/index.ts index 89ca4cb7f..c84fd60bd 100644 --- a/packages/gleequore/src/adapters/cluster/redis/index.ts +++ b/packages/redis-cluster-adapter/index.ts @@ -1,11 +1,10 @@ import { createClient } from 'redis' -import ClusterAdapter from '../../../lib/cluster.js' -import GleeQuoreMessage from '../../../lib/message.js' +import { GleeQuoreClusterAdapter, GleeQuoreMessage } from '@asyncapi/gleequore' const client = createClient() type RedisClientType = typeof client -class RedisClusterAdapter extends ClusterAdapter { +class RedisClusterAdapter extends GleeQuoreClusterAdapter { private _channelName: string private _publisher: RedisClientType diff --git a/packages/redis-cluster-adapter/package.json b/packages/redis-cluster-adapter/package.json new file mode 100644 index 000000000..faaf1ac8d --- /dev/null +++ b/packages/redis-cluster-adapter/package.json @@ -0,0 +1,43 @@ +{ + "name": "@asyncapi/glee-redis-cluster-adapter", + "version": "0.1.0", + "description": "Redis Cluster adapter for Glee.", + "exports": "./dist/index.js", + "type": "module", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "scripts": { + "build": "tsc", + "bump:version": "npm --no-git-tag-version --allow-same-version version $VERSION", + "dev": "tsc --watch", + "docs": "typedoc --readme none --githubPages false --out docs/reference --entryPointStrategy expand ./src", + "get:version": "echo $npm_package_version", + "get:name": "echo $npm_package_name", + "generate:assets": "npm run docs", + "lint": "eslint --max-warnings 1 --config .eslintrc .", + "lint:fix": "eslint --max-warnings 1 --config .eslintrc . --fix", + "prepublishOnly": "npm run build", + "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", + "test:dev": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch" + }, + "dependencies": { + "@asyncapi/gleequore": "workspace:*", + "@asyncapi/glee-shared-utils": "workspace:*", + "redis": "^4.0.2" + }, + "devDependencies": { + "@tsconfig/node14": "^1.0.1", + "@types/debug": "^4.1.7", + "@typescript-eslint/eslint-plugin": "^5.9.0", + "@typescript-eslint/parser": "^5.9.0", + "eslint": "^8.6.0", + "eslint-plugin-github": "^4.3.5", + "eslint-plugin-security": "^1.4.0", + "eslint-plugin-sonarjs": "^0.19.0", + "typedoc": "^0.26.10", + "typedoc-plugin-markdown": "^4.2.9", + "typescript": "^4.5.4" + }, + "license": "Apache-2.0" +} diff --git a/packages/redis-cluster-adapter/tsconfig.json b/packages/redis-cluster-adapter/tsconfig.json new file mode 100644 index 000000000..28a356c9d --- /dev/null +++ b/packages/redis-cluster-adapter/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "skipLibCheck": true, + "outDir": "./dist", + "allowJs": false, + "target": "es6", + "esModuleInterop": true, + "moduleResolution": "nodenext", + "module": "NodeNext" + }, + "include": [ + "./*.ts" + ], + "exclude": [ + "node_modules" + ] +} \ No newline at end of file diff --git a/packages/shared-utils/.editorconfig b/packages/shared-utils/.editorconfig new file mode 100644 index 000000000..3abcad16c --- /dev/null +++ b/packages/shared-utils/.editorconfig @@ -0,0 +1,17 @@ +# EditorConfig + +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +[Makefile] +indent_style = tab + +[*.md] +trim_trailing_whitespace = false diff --git a/packages/shared-utils/.eslintignore b/packages/shared-utils/.eslintignore new file mode 100644 index 000000000..fa806b632 --- /dev/null +++ b/packages/shared-utils/.eslintignore @@ -0,0 +1,9 @@ +node_modules +examples/social-network/frontend +examples/crypto-websockets +dist +.glee +test +jest.config.js +docs +try.js \ No newline at end of file diff --git a/packages/shared-utils/.eslintrc b/packages/shared-utils/.eslintrc new file mode 100644 index 000000000..02bbf610b --- /dev/null +++ b/packages/shared-utils/.eslintrc @@ -0,0 +1,53 @@ +{ + "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint", + "sonarjs", + "security", + "github" + ], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "plugin:sonarjs/recommended", + "plugin:security/recommended" + ], + "rules": { + "curly": [ + "error", + "multi-line" + ], + "security/detect-non-literal-fs-filename": "off", + "@typescript-eslint/no-explicit-any": "off", + "semi": [ + "warn", + "never" + ], + "sonarjs/cognitive-complexity": "warn" + }, + "globals": { + "process": "readonly" + }, + "overrides": [ + { + "files": [ + "*.spec.ts", + "*.spec.tsx", + "src/help/command.ts" + ], + "rules": { + "no-undef": "off", + "security/detect-non-literal-fs-filename": "off", + "sonarjs/no-duplicate-string": "off", + "security/detect-object-injection": "off", + "max-nested-callbacks": "off", + "sonarjs/no-identical-functions": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-unused-vars": "off", + "no-use-before-define": "off", + "sonarjs/cognitive-complexity": "off" + } + } + ] +} \ No newline at end of file diff --git a/packages/shared-utils/.prettierignore b/packages/shared-utils/.prettierignore new file mode 100644 index 000000000..764aff77b --- /dev/null +++ b/packages/shared-utils/.prettierignore @@ -0,0 +1,6 @@ +dist +coverage +.github +examples +node_modules +docs diff --git a/packages/shared-utils/.prettierrc.yaml b/packages/shared-utils/.prettierrc.yaml new file mode 100644 index 000000000..45c45fea2 --- /dev/null +++ b/packages/shared-utils/.prettierrc.yaml @@ -0,0 +1,4 @@ +trailingComma: "es5" +tabWidth: 2 +semi: false +singleQuote: true \ No newline at end of file diff --git a/packages/gleequore/src/lib/util.ts b/packages/shared-utils/index.ts similarity index 65% rename from packages/gleequore/src/lib/util.ts rename to packages/shared-utils/index.ts index 8232a1eef..035939fdd 100644 --- a/packages/gleequore/src/lib/util.ts +++ b/packages/shared-utils/index.ts @@ -2,7 +2,6 @@ import { AsyncAPIDocumentInterface as AsyncAPIDocument, ChannelInterface, Channe import Ajv from 'ajv' import betterAjvErrors from 'better-ajv-errors' import { pathToRegexp } from 'path-to-regexp' -import GleeQuoreMessage from './message.js' interface IValidateDataReturn { errors?: void | betterAjvErrors.IOutputError[] @@ -40,36 +39,6 @@ export const getParams = ( ) } -/** - * Duplicates a GleeMessage. - * - * @private - * @param {GleeQuoreMessage} message The message to duplicate. - * @return {GleeQuoreMessage} - */ -export const duplicateMessage = (message: GleeQuoreMessage): GleeQuoreMessage => { - const newMessage = new GleeQuoreMessage({ - operation: message.operation, - payload: message.payload, - headers: message.headers, - channel: message.channel, - request: message.request, - serverName: message.serverName, - connection: message.connection, - broadcast: message.broadcast, - cluster: message.cluster, - query: message.query, - }) - - if (message.isInbound()) { - newMessage.setInbound() - } else { - newMessage.setOutbound() - } - - return newMessage -} - /** * Determines if a path matches a channel. * @@ -178,42 +147,6 @@ export function extractExpressionValueFromMessage(message: { headers: any, paylo } } -export function applyAddressParameters(channel: ChannelInterface, message?: GleeQuoreMessage): string { - let address = channel.address() - const parameters = channel.parameters() - for (const parameter of parameters) { - address = substituteParameterInAddress(parameter, address, message) - } - return address -} - -const substituteParameterInAddress = (parameter: ChannelParameterInterface, address: string, message: GleeQuoreMessage): string => { - const doesExistInAddress = address.includes(`{${parameter.id()}}`) - if (!doesExistInAddress) return address - const parameterValue = getParamValue(parameter, message) - if (!parameterValue) { - throw Error(`parsing parameter "${parameter.id()}" value failed. please make sure it exists in your header/payload or in default field of the parameter.`) - } - address = address.replace(`{${parameter.id()}}`, parameterValue) - return address -} - -const getParamValue = (parameter: ChannelParameterInterface, message: GleeQuoreMessage): string | null => { - const location = parameter.location() - if (!location) return parameter.json().default - const paramFromLocation = getParamFromLocation(location, message) - if (!paramFromLocation) { - return parameter.json().default - } - return paramFromLocation -} - -function getParamFromLocation(location: string, message: GleeQuoreMessage) { - if ((message.payload || message.headers) && location) { - return extractExpressionValueFromMessage(message, location) - } -} - export function getMessagesSchema(operation: { messages: () => MessagesInterface }) { const messagesSchemas = operation.messages().all().map(m => m.payload().json()).filter(schema => !!schema) return { diff --git a/packages/shared-utils/package.json b/packages/shared-utils/package.json new file mode 100644 index 000000000..cd1cb76bb --- /dev/null +++ b/packages/shared-utils/package.json @@ -0,0 +1,30 @@ +{ + "name": "@asyncapi/glee-shared-utils", + "version": "0.1.0", + "description": "Shared utilities for Glee", + "main": "dist/index.js", + "type": "module", + "scripts": { + "build": "tsc", + "dev": "tsc --watch", + "get:version": "echo $npm_package_version", + "get:name": "echo $npm_package_name", + "generate:assets": "echo 'No generate:assets script. Ignoring...'", + "lint": "eslint --max-warnings 1 --config .eslintrc .", + "lint:fix": "eslint --max-warnings 1 --config .eslintrc . --fix", + "prepublishOnly": "npm run build", + "test": "echo 'No test script. Ignoring...'", + "test:dev": "echo 'No test:dev script. Ignoring...'" + }, + "author": "Fran Mendez (https://fmvilas.me)", + "license": "ISC", + "dependencies": { + "@asyncapi/parser": "^3.1.0", + "ajv": "^6.12.6", + "better-ajv-errors": "^0.7.0", + "path-to-regexp": "^6.2.0" + }, + "devDependencies": { + "typescript": "^4.5.4" + } +} diff --git a/packages/shared-utils/tsconfig.json b/packages/shared-utils/tsconfig.json new file mode 100644 index 000000000..28a356c9d --- /dev/null +++ b/packages/shared-utils/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "skipLibCheck": true, + "outDir": "./dist", + "allowJs": false, + "target": "es6", + "esModuleInterop": true, + "moduleResolution": "nodenext", + "module": "NodeNext" + }, + "include": [ + "./*.ts" + ], + "exclude": [ + "node_modules" + ] +} \ No newline at end of file diff --git a/packages/socket.io-adapter/README.md b/packages/socket.io-adapter/README.md new file mode 100644 index 000000000..16cca3b24 --- /dev/null +++ b/packages/socket.io-adapter/README.md @@ -0,0 +1,3 @@ +# socket.io-adapter + +Socket.IO adapter for Glee. diff --git a/packages/gleequore/src/adapters/socket.io/index.ts b/packages/socket.io-adapter/index.ts similarity index 93% rename from packages/gleequore/src/adapters/socket.io/index.ts rename to packages/socket.io-adapter/index.ts index 08b461b0f..543ca7ae7 100644 --- a/packages/gleequore/src/adapters/socket.io/index.ts +++ b/packages/socket.io-adapter/index.ts @@ -1,8 +1,7 @@ import { Server } from 'socket.io' -import Adapter from '../../lib/adapter.js' -import GleeQuoreMessage from '../../lib/message.js' +import { GleeQuoreAdapter, GleeQuoreMessage } from '@asyncapi/gleequore' -class SocketIOAdapter extends Adapter { +class SocketIOAdapter extends GleeQuoreAdapter { private server: Server name(): string { @@ -18,7 +17,7 @@ class SocketIOAdapter extends Adapter { } async _connect(): Promise { - const config = await this.resolveProtocolConfig('ws') + const config = await this.resolveConfig() const websocketOptions = config?.server const serverUrl: URL = new URL(this.serverUrlExpanded) const asyncapiServerPort: number = serverUrl.port diff --git a/packages/socket.io-adapter/package.json b/packages/socket.io-adapter/package.json new file mode 100644 index 000000000..6018389b8 --- /dev/null +++ b/packages/socket.io-adapter/package.json @@ -0,0 +1,43 @@ +{ + "name": "@asyncapi/glee-socketio-adapter", + "version": "0.1.0", + "description": "Socket.IO adapter for Glee.", + "exports": "./dist/index.js", + "type": "module", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "scripts": { + "build": "tsc", + "bump:version": "npm --no-git-tag-version --allow-same-version version $VERSION", + "dev": "tsc --watch", + "docs": "typedoc --readme none --githubPages false --out docs/reference --entryPointStrategy expand ./src", + "get:version": "echo $npm_package_version", + "get:name": "echo $npm_package_name", + "generate:assets": "npm run docs", + "lint": "eslint --max-warnings 1 --config .eslintrc .", + "lint:fix": "eslint --max-warnings 1 --config .eslintrc . --fix", + "prepublishOnly": "npm run build", + "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", + "test:dev": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch" + }, + "dependencies": { + "@asyncapi/gleequore": "workspace:*", + "@asyncapi/glee-shared-utils": "workspace:*", + "socket.io": "^4.1.2" + }, + "devDependencies": { + "@tsconfig/node14": "^1.0.1", + "@types/debug": "^4.1.7", + "@typescript-eslint/eslint-plugin": "^5.9.0", + "@typescript-eslint/parser": "^5.9.0", + "eslint": "^8.6.0", + "eslint-plugin-github": "^4.3.5", + "eslint-plugin-security": "^1.4.0", + "eslint-plugin-sonarjs": "^0.19.0", + "typedoc": "^0.26.10", + "typedoc-plugin-markdown": "^4.2.9", + "typescript": "^4.5.4" + }, + "license": "Apache-2.0" +} diff --git a/packages/socket.io-adapter/tsconfig.json b/packages/socket.io-adapter/tsconfig.json new file mode 100644 index 000000000..28a356c9d --- /dev/null +++ b/packages/socket.io-adapter/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "skipLibCheck": true, + "outDir": "./dist", + "allowJs": false, + "target": "es6", + "esModuleInterop": true, + "moduleResolution": "nodenext", + "module": "NodeNext" + }, + "include": [ + "./*.ts" + ], + "exclude": [ + "node_modules" + ] +} \ No newline at end of file diff --git a/packages/web-adapter/README.md b/packages/web-adapter/README.md new file mode 100644 index 000000000..69b078409 --- /dev/null +++ b/packages/web-adapter/README.md @@ -0,0 +1,3 @@ +# web-adapter + +HTTP and WebSocket client adapter for Glee. diff --git a/packages/gleequore/src/adapters/http/client.ts b/packages/web-adapter/http.ts similarity index 86% rename from packages/gleequore/src/adapters/http/client.ts rename to packages/web-adapter/http.ts index 4259d82e7..54c4bbbdf 100644 --- a/packages/gleequore/src/adapters/http/client.ts +++ b/packages/web-adapter/http.ts @@ -1,14 +1,11 @@ import got, { Method } from 'got' import http from 'http' -import Adapter from '../../lib/adapter.js' -import GleeQuoreMessage from '../../lib/message.js' -import GleeQuoreAuth from '../../lib/wsHttpAuth.js' +import { GleeQuoreAdapter, GleeQuoreMessage } from '@asyncapi/gleequore' import { ChannelInterface, OperationInterface } from '@asyncapi/parser' -import { Authenticatable } from '../../index.d.js' -import { validateData } from '../../lib/util.js' -import GleeError from '../../errors.js' +import type { Authenticatable } from '@asyncapi/gleequore' +import GleeQuoreAuth from './wsHttpAuth.js' -class HttpClientAdapter extends Adapter { +class HttpClientAdapter extends GleeQuoreAdapter { name(): string { return 'HTTP client' } @@ -103,21 +100,15 @@ class HttpClientAdapter extends Adapter { _validateMessage(message: GleeQuoreMessage) { const querySchema = message.operation.bindings().get("http")?.json()?.query - if (querySchema) this._validate(message.query, querySchema) + if (querySchema) this.validate(message.query, querySchema, true) const messages = message.operation.messages().all() if (!messages.length) return const headersSchema = { oneOf: messages.map(message => message?.bindings()?.get("http")?.json()?.headers).filter(header => !!header) } - if (headersSchema.oneOf.length > 0) this._validate(message.headers, headersSchema) + if (headersSchema.oneOf.length > 0) this.validate(message.headers, headersSchema, true) } - _validate(data, schema) { - const { isValid, errors, humanReadableError } = validateData(data, schema) - if (!isValid) { - throw new GleeError({ humanReadableError, errors }) - } - } _shouldMethodHaveBody(method: Method) { return ["post", "put", "patch"].includes(method.toLocaleLowerCase()) } diff --git a/packages/web-adapter/index.d.ts b/packages/web-adapter/index.d.ts new file mode 100644 index 000000000..cbeb92251 --- /dev/null +++ b/packages/web-adapter/index.d.ts @@ -0,0 +1,36 @@ +import { GleeQuoreAdapterOptions } from "@asyncapi/gleequore" + +export type WsHttpAuth = WsAuthConfig | HttpAuthConfig + +export interface HttpAuthConfig { + token?: string + username?: string + password?: string +} + +export interface WsAuthConfig { + token?: string + username?: string + password?: string +} + +export type AuthProps = { + getToken: () => string + getUserPass: () => { + username: string + password: string + } + getCert: () => string + getOauthToken: () => string + getHttpAPIKeys: (name: string) => string + getAPIKeys: () => string +} + +type Headers = { [key: string]: string } +type QueryParam = { [key: string]: string } | { [key: string]: string[] } + +export interface Authenticatable { + headers: Headers, + query: QueryParam + url: URL +} \ No newline at end of file diff --git a/packages/web-adapter/package.json b/packages/web-adapter/package.json new file mode 100644 index 000000000..69ea8885e --- /dev/null +++ b/packages/web-adapter/package.json @@ -0,0 +1,48 @@ +{ + "name": "@asyncapi/glee-web-adapter", + "version": "0.1.0", + "description": "HTTP and WebSocket client adapter for Glee.", + "exports": { + "./http": "./dist/http.js", + "./ws": "./dist/ws.js" + }, + "type": "module", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "scripts": { + "build": "tsc", + "bump:version": "npm --no-git-tag-version --allow-same-version version $VERSION", + "dev": "tsc --watch", + "docs": "typedoc --readme none --githubPages false --out docs/reference --entryPointStrategy expand ./src", + "get:version": "echo $npm_package_version", + "get:name": "echo $npm_package_name", + "generate:assets": "npm run docs", + "lint": "eslint --max-warnings 1 --config .eslintrc .", + "lint:fix": "eslint --max-warnings 1 --config .eslintrc . --fix", + "prepublishOnly": "npm run build", + "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", + "test:dev": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch" + }, + "dependencies": { + "@asyncapi/glee-shared-utils": "workspace:*", + "@asyncapi/gleequore": "workspace:*", + "got": "^12.5.3", + "ws": "^7.4.6" + }, + "devDependencies": { + "@tsconfig/node14": "^1.0.1", + "@types/debug": "^4.1.7", + "@types/ws": "^8.5.3", + "@typescript-eslint/eslint-plugin": "^5.9.0", + "@typescript-eslint/parser": "^5.9.0", + "eslint": "^8.6.0", + "eslint-plugin-github": "^4.3.5", + "eslint-plugin-security": "^1.4.0", + "eslint-plugin-sonarjs": "^0.19.0", + "typedoc": "^0.26.10", + "typedoc-plugin-markdown": "^4.2.9", + "typescript": "^4.5.4" + }, + "license": "Apache-2.0" +} diff --git a/packages/web-adapter/tsconfig.json b/packages/web-adapter/tsconfig.json new file mode 100644 index 000000000..b5a5a8840 --- /dev/null +++ b/packages/web-adapter/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "skipLibCheck": true, + "outDir": "./dist", + "allowJs": false, + "target": "es6", + "esModuleInterop": true, + "moduleResolution": "nodenext", + "module": "NodeNext" + }, + "include": [ + "./*.ts" +, "http/client.ts", "http/server.ts" ], + "exclude": [ + "node_modules" + ] +} \ No newline at end of file diff --git a/packages/web-adapter/utils.ts b/packages/web-adapter/utils.ts new file mode 100644 index 000000000..24daa9c5d --- /dev/null +++ b/packages/web-adapter/utils.ts @@ -0,0 +1,39 @@ +import type { ChannelInterface, ChannelParameterInterface } from "@asyncapi/parser" +import type { GleeQuoreMessage} from "@asyncapi/gleequore" +import { extractExpressionValueFromMessage } from "@asyncapi/glee-shared-utils" + +export function applyAddressParameters(channel: ChannelInterface, message?: GleeQuoreMessage): string { + let address = channel.address() + const parameters = channel.parameters() + for (const parameter of parameters) { + address = substituteParameterInAddress(parameter, address, message) + } + return address +} + +export function substituteParameterInAddress(parameter: ChannelParameterInterface, address: string, message?: GleeQuoreMessage): string { + const doesExistInAddress = address.includes(`{${parameter.id()}}`) + if (!doesExistInAddress) return address + const parameterValue = getParamValue(parameter, message) + if (!parameterValue) { + throw Error(`parsing parameter "${parameter.id()}" value failed. please make sure it exists in your header/payload or in default field of the parameter.`) + } + address = address.replace(`{${parameter.id()}}`, parameterValue) + return address +} + +const getParamValue = (parameter: ChannelParameterInterface, message: GleeQuoreMessage): string | null => { + const location = parameter.location() + if (!location) return parameter.json().default + const paramFromLocation = getParamFromLocation(location, message) + if (!paramFromLocation) { + return parameter.json().default + } + return paramFromLocation +} + +function getParamFromLocation(location: string, message: GleeQuoreMessage) { + if ((message.payload || message.headers) && location) { + return extractExpressionValueFromMessage(message, location) + } +} \ No newline at end of file diff --git a/packages/gleequore/src/adapters/ws/client.ts b/packages/web-adapter/ws.ts similarity index 73% rename from packages/gleequore/src/adapters/ws/client.ts rename to packages/web-adapter/ws.ts index e77a48cff..085701e2d 100644 --- a/packages/gleequore/src/adapters/ws/client.ts +++ b/packages/web-adapter/ws.ts @@ -1,10 +1,10 @@ /* eslint-disable security/detect-object-injection */ -import Adapter from '../../lib/adapter.js' -import GleeQuoreMessage from '../../lib/message.js' +import { GleeQuoreAdapter, GleeQuoreMessage } from '@asyncapi/gleequore' import ws from 'ws' -import GleeQuoreAuth from '../../lib/wsHttpAuth.js' -import { applyAddressParameters } from '../../lib/util.js' +import GleeQuoreAuth from './wsHttpAuth.js' import Debug from 'debug' +import { ChannelInterface } from '@asyncapi/parser' +import { substituteParameterInAddress } from './utils.js' const debug = Debug("glee:ws:client") interface Client { channel: string @@ -12,7 +12,7 @@ interface Client { binding?: any } -class WsClientAdapter extends Adapter { +class WsClientAdapter extends GleeQuoreAdapter { private clients: Array = [] name(): string { @@ -28,7 +28,7 @@ class WsClientAdapter extends Adapter { } private async _connect(): Promise { - const channelsOnThisServer = this.getWsChannels() + const channelsOnThisServer: string[] = this.getWsChannels() debug("connecting to ", this.serverName) for (const channelName of channelsOnThisServer) { @@ -44,17 +44,18 @@ class WsClientAdapter extends Adapter { const protocol = this.AsyncAPIServer.protocol() const serverHost = this.AsyncAPIServer.host() const channel = this.parsedAsyncAPI.channels().get(channelName) + if (!channel) continue const channelAddress = applyAddressParameters(channel) let url = new URL(`${protocol}://${serverHost}${channelAddress}`) if (authConfig) { const modedAuth = await gleeAuth.processClientAuth({ url, headers, query: {} }) - headers = modedAuth.headers - url = modedAuth.url + headers = modedAuth?.headers ? modedAuth.headers : headers + url = modedAuth?.url ? modedAuth.url : url } this.clients.push({ channel: channelName, client: new ws(url, { headers }), - binding: this.parsedAsyncAPI.channels().get(channelName).bindings().get('ws'), + binding: this.parsedAsyncAPI.channels().get(channelName)?.bindings().get('ws'), }) } @@ -82,18 +83,18 @@ class WsClientAdapter extends Adapter { return this } - private getWsChannels() { - const channels = [] + private getWsChannels(): string[] { + const channels: string[] = [] for (const channel of this.channelNames) { - if (this.parsedAsyncAPI.channels().get(channel).servers().all().length !== 0) { // NOSONAR + if (this.parsedAsyncAPI.channels().get(channel)?.servers().all().length !== 0) { // NOSONAR if ( this.parsedAsyncAPI .channels().get(channel) - .servers().get(this.serverName) + ?.servers().get(this.serverName) ) { channels.push(channel) } - } else { + } else if (channel) { channels.push(channel) } } @@ -122,4 +123,14 @@ class WsClientAdapter extends Adapter { } } +function applyAddressParameters(channel: ChannelInterface, message?: GleeQuoreMessage): string | undefined { + let address = channel.address() + if (!address) return undefined + + for (const parameter of channel.parameters()) { + address = substituteParameterInAddress(parameter, address, message) + } + return address +} + export default WsClientAdapter diff --git a/packages/web-adapter/wsHttpAuth.ts b/packages/web-adapter/wsHttpAuth.ts new file mode 100644 index 000000000..37e291df8 --- /dev/null +++ b/packages/web-adapter/wsHttpAuth.ts @@ -0,0 +1,133 @@ +import { AsyncAPIDocumentInterface as AsyncAPIDocument, ServerInterface } from '@asyncapi/parser' +import { resolveFunctions } from '@asyncapi/glee-shared-utils' +import { EventEmitter } from 'events' +import { HttpAuthConfig, WsAuthConfig, AuthProps, Authenticatable } from './index.d.js' + +class GleeQuoreAuth extends EventEmitter { + private parsedAsyncAPI: AsyncAPIDocument + private serverName: string + private AsyncAPIServer: ServerInterface + private authConfig: WsAuthConfig | HttpAuthConfig + private auth: { [key: string]: string } | { [key: string]: string[] } + + /** + * Instantiates authentication. + */ + constructor( + AsyncAPIServer: ServerInterface, + parsedAsyncAPI: AsyncAPIDocument, + serverName: string, + authConfig? + ) { + super() + this.parsedAsyncAPI = parsedAsyncAPI + this.serverName = serverName + this.AsyncAPIServer = AsyncAPIServer + this.authConfig = authConfig + } + + checkClientAuthConfig() { + const securitySchemeID = this.parsedAsyncAPI.securitySchemes().all().map(s => s.id()) + + const authKeys = Object.keys(this.auth) + authKeys.forEach(authKey => { + const allowed = securitySchemeID.includes(authKey) + if (!allowed) { + const err = new Error(`${authKey} securityScheme is not defined is your asyncapi.yaml config`) + this.emit('error', err) + } + }) + + return authKeys + } + + async getAuthConfig(auth) { + if (!auth) return + if (typeof auth !== 'function') { + await resolveFunctions(auth) + return auth + } + + return await auth({ + serverName: this.serverName, + parsedAsyncAPI: this.parsedAsyncAPI, + }) + } + + formClientAuth(authKeys, { url, headers, query }) { + if (!authKeys) return { url, headers } + authKeys.map((authKey) => { + const scheme = this.parsedAsyncAPI.securitySchemes().get(authKey) + const currentScheme = scheme.scheme() + const currentType = scheme.type() + if (currentScheme == 'bearer') { + headers.authentication = `bearer ${this.auth[String(authKey)]}` + return + } + if (currentType == 'userPassword' || currentType == 'apiKey') { + url = this.userPassApiKeyLogic(url, authKey) + return + } + if (currentType == 'oauth2') { + headers.oauthToken = this.auth[String(authKey)] + } + if (currentType == 'httpApiKey') { + const conf = this.httpApiKeyLogic(scheme, headers, query, authKey) + headers = conf.headers + query = conf.query + + if (query) { + Object.keys(query).forEach(k => { + // eslint-disable-next-line security/detect-object-injection + url.searchParams.set(k, query[k]) + }) + } + } + }) + return { url, headers, query } + } + + private userPassApiKeyLogic(url, authKey) { + const password = this.auth[String(authKey)]['password'] + const username = this.auth[String(authKey)]['user'] + + if (typeof url == 'object') { + url.password = password + url.username = username + return url + } + + const myURL = new URL(url) + myURL.password = password + myURL.username = username + return myURL + } + + private httpApiKeyLogic(scheme, headers, query, authKey) { + + const loc = scheme.in() + if (loc == 'header') { + headers[scheme.name()] = this.auth[String(authKey)] + } else if (loc == 'query') { + query[scheme.name()] = this.auth[String(authKey)] + } + + return { headers, query } + } + + async processClientAuth({ url, headers, query }: Authenticatable) { + this.auth = await this.getAuthConfig(this.authConfig) + const authKeys = this.checkClientAuthConfig() + if (!authKeys) return + return this.formClientAuth(authKeys, { url, headers, query }) + } + + checkAuthPresense(): boolean { + return ( + this.AsyncAPIServer.security() && + Object.keys(this.AsyncAPIServer.security()).length > 0 + ) + } +} + +export default GleeQuoreAuth diff --git a/packages/web-server-adapter/README.md b/packages/web-server-adapter/README.md new file mode 100644 index 000000000..2c54322d5 --- /dev/null +++ b/packages/web-server-adapter/README.md @@ -0,0 +1,4 @@ +# web-server-adapter + +HTTP and WebSocket server adapter for Glee. + diff --git a/packages/gleequore/src/adapters/http/server.ts b/packages/web-server-adapter/http.ts similarity index 95% rename from packages/gleequore/src/adapters/http/server.ts rename to packages/web-server-adapter/http.ts index 2d525583b..316d34e9a 100644 --- a/packages/gleequore/src/adapters/http/server.ts +++ b/packages/web-server-adapter/http.ts @@ -1,14 +1,11 @@ -import Adapter from '../../lib/adapter.js' -import GleeQuoreMessage from '../../lib/message.js' +import { GleeQuoreAdapter, GleeQuoreMessage } from '@asyncapi/gleequore' import http, { IncomingMessage, ServerResponse } from 'http' import { StringDecoder } from 'string_decoder' -import { validateData } from '../../lib/util.js' import * as url from 'url' -import GleeQuoreAuth from '../../lib/wsHttpAuth.js' import { ChannelInterface } from '@asyncapi/parser' +import GleeQuoreAuth from './wsHttpAuth.js' - -class HttpAdapter extends Adapter { +class HttpAdapter extends GleeQuoreAdapter { private httpResponses = new Map() name(): string { @@ -162,7 +159,7 @@ class HttpAdapter extends Adapter { } async _connect(): Promise { - const config = await this.resolveProtocolConfig('http') + const config = await this.resolveConfig() const httpOptions = config?.server const httpServer = httpOptions?.httpServer || http.createServer() const asyncapiServerPort = new URL(this.serverUrlExpanded).port || 80 @@ -196,7 +193,7 @@ class HttpAdapter extends Adapter { const schema = { anyOf: querySchemas } const { query } = url.parse(req.url, true) - const { isValid, humanReadableError } = validateData( + const { isValid, humanReadableError } = this.validate( query, schema ) @@ -217,7 +214,7 @@ class HttpAdapter extends Adapter { const schema = { anyOf: headerSchemas } const headers = req.headers - const { isValid, humanReadableError } = validateData( + const { isValid, humanReadableError } = this.validate( headers, schema ) diff --git a/packages/web-server-adapter/index.d.ts b/packages/web-server-adapter/index.d.ts new file mode 100644 index 000000000..db2fa4a94 --- /dev/null +++ b/packages/web-server-adapter/index.d.ts @@ -0,0 +1,42 @@ +import { GleeQuoreAdapterOptions } from "@asyncapi/gleequore" + +export type WsHttpAuth = WsAuthConfig | HttpAuthConfig + +export interface HttpAuthConfig { + token?: string + username?: string + password?: string +} + +export interface WsAuthConfig { + token?: string + username?: string + password?: string +} + +export interface WebsocketServerAdapterConfig extends GleeQuoreAdapterOptions { + httpServer?: any + adapter?: WebSocketServerType | typeof GleeQuoreAdapter + port?: number +} + +export type AuthProps = { + getToken: () => string + getUserPass: () => { + username: string + password: string + } + getCert: () => string + getOauthToken: () => string + getHttpAPIKeys: (name: string) => string + getAPIKeys: () => string +} + +type Headers = { [key: string]: string } +type QueryParam = { [key: string]: string } | { [key: string]: string[] } + +export interface Authenticatable { + headers: Headers, + query: QueryParam + url: URL +} \ No newline at end of file diff --git a/packages/web-server-adapter/package.json b/packages/web-server-adapter/package.json new file mode 100644 index 000000000..2c04d792f --- /dev/null +++ b/packages/web-server-adapter/package.json @@ -0,0 +1,47 @@ +{ + "name": "@asyncapi/glee-web-server-adapter", + "version": "0.1.0", + "description": "HTTP and WebSocket server adapter for Glee.", + "exports": { + "./http": "./dist/http.js", + "./ws": "./dist/ws.js" + }, + "type": "module", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "scripts": { + "build": "tsc", + "bump:version": "npm --no-git-tag-version --allow-same-version version $VERSION", + "dev": "tsc --watch", + "docs": "typedoc --readme none --githubPages false --out docs/reference --entryPointStrategy expand ./src", + "get:version": "echo $npm_package_version", + "get:name": "echo $npm_package_name", + "generate:assets": "npm run docs", + "lint": "eslint --max-warnings 1 --config .eslintrc .", + "lint:fix": "eslint --max-warnings 1 --config .eslintrc . --fix", + "prepublishOnly": "npm run build", + "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", + "test:dev": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch" + }, + "dependencies": { + "@asyncapi/glee-shared-utils": "workspace:*", + "@asyncapi/gleequore": "workspace:*", + "ws": "^7.4.6" + }, + "devDependencies": { + "@tsconfig/node14": "^1.0.1", + "@types/debug": "^4.1.7", + "@types/ws": "^8.5.3", + "@typescript-eslint/eslint-plugin": "^5.9.0", + "@typescript-eslint/parser": "^5.9.0", + "eslint": "^8.6.0", + "eslint-plugin-github": "^4.3.5", + "eslint-plugin-security": "^1.4.0", + "eslint-plugin-sonarjs": "^0.19.0", + "typedoc": "^0.26.10", + "typedoc-plugin-markdown": "^4.2.9", + "typescript": "^4.5.4" + }, + "license": "Apache-2.0" +} diff --git a/packages/web-server-adapter/tsconfig.json b/packages/web-server-adapter/tsconfig.json new file mode 100644 index 000000000..b5a5a8840 --- /dev/null +++ b/packages/web-server-adapter/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "skipLibCheck": true, + "outDir": "./dist", + "allowJs": false, + "target": "es6", + "esModuleInterop": true, + "moduleResolution": "nodenext", + "module": "NodeNext" + }, + "include": [ + "./*.ts" +, "http/client.ts", "http/server.ts" ], + "exclude": [ + "node_modules" + ] +} \ No newline at end of file diff --git a/packages/gleequore/src/adapters/ws/server.ts b/packages/web-server-adapter/ws.ts similarity index 92% rename from packages/gleequore/src/adapters/ws/server.ts rename to packages/web-server-adapter/ws.ts index 08bf94b06..fd012592d 100644 --- a/packages/gleequore/src/adapters/ws/server.ts +++ b/packages/web-server-adapter/ws.ts @@ -1,15 +1,13 @@ import WebSocket from 'ws' import http, { IncomingMessage, Server as HttpServer } from 'http' import type { Duplex } from 'stream' -import { validateData } from '../../lib/util.js' -import Adapter, { GleeQuoreAdapterOptions } from '../../lib/adapter.js' -import GleeQuoreConnection from '../../lib/connection.js' -import GleeQuoreMessage from '../../lib/message.js' -import GleeQuoreAuth from '../../lib/wsHttpAuth.js' -import { WebsocketServerAdapterConfig } from '../../index.d.js' +import { validateData } from '@asyncapi/glee-shared-utils' +import { GleeQuoreAdapter, GleeQuoreMessage, GleeQuoreConnection, GleeQuoreAdapterOptions } from '@asyncapi/gleequore' +import GleeQuoreAuth from './wsHttpAuth.js' +import { WebsocketServerAdapterConfig } from './index.d.js' import * as url from 'url' -class WebSocketsAdapter extends Adapter { +export default class WebSocketsServerAdapter extends GleeQuoreAdapter { private config: WebsocketServerAdapterConfig private serverUrl: URL private wsHttpServer: HttpServer @@ -17,15 +15,14 @@ class WebSocketsAdapter extends Adapter { // WebSockets are limited to a single connection path per server. To accommodate multiple channels, we instantiate a separate server for each channel and maintain a record of these servers here. private websocketServers: Map - - constructor(options: GleeQuoreAdapterOptions) { - super(options) - this.config = this.app.options?.ws?.server + constructor(options: WebsocketServerAdapterConfig) { + const { httpServer, adapter, port, ...rest } = options + super(rest as GleeQuoreAdapterOptions) + this.config = options this.customHttpServer = this.config?.httpServer this.wsHttpServer = this.customHttpServer || http.createServer() this.serverUrl = new URL(this.serverUrlExpanded) this.websocketServers = new Map() - } name(): string { @@ -35,11 +32,11 @@ class WebSocketsAdapter extends Adapter { async connect(): Promise { try { await this._connect() - return this } catch (e) { const errorMessage = `Unable to connect to ${this.name()}: ${e.message}` this.emit('error', new Error(errorMessage)) } + return this } private _createServers() { @@ -96,7 +93,7 @@ class WebSocketsAdapter extends Adapter { const pathName = this._extractPathname(req) return this.parsedAsyncAPI.channels().all().filter(channel => { let address = channel.address() - if (address.endsWith('/')) address = address.substring(0, address.length - 1) + if (address && address.endsWith('/')) address = address.substring(0, address.length - 1) return address === pathName })[0] @@ -151,7 +148,7 @@ class WebSocketsAdapter extends Adapter { private _validateQueries(req: IncomingMessage, socket: Duplex, channelBindings) { const schema = channelBindings.query if (!schema) return - const { query } = url.parse(req.url, true) + const { query } = url.parse(req.url || '', true) const { isValid, humanReadableError } = validateData( query, schema @@ -187,7 +184,7 @@ class WebSocketsAdapter extends Adapter { private _extractPathname(req: IncomingMessage) { const serverUrl = new URL(this.serverUrlExpanded) - const { pathname } = new URL(req.url, serverUrl) + const { pathname } = new URL(req.url || '', serverUrl) if (!pathname) return '/' @@ -299,4 +296,3 @@ class WebSocketsAdapter extends Adapter { } } -export default WebSocketsAdapter diff --git a/packages/gleequore/src/lib/wsHttpAuth.ts b/packages/web-server-adapter/wsHttpAuth.ts similarity index 98% rename from packages/gleequore/src/lib/wsHttpAuth.ts rename to packages/web-server-adapter/wsHttpAuth.ts index 1e690171c..371d506e2 100644 --- a/packages/gleequore/src/lib/wsHttpAuth.ts +++ b/packages/web-server-adapter/wsHttpAuth.ts @@ -1,7 +1,7 @@ import { AsyncAPIDocumentInterface as AsyncAPIDocument, ServerInterface } from '@asyncapi/parser' -import { resolveFunctions } from './util.js' +import { resolveFunctions } from '@asyncapi/glee-shared-utils' import { EventEmitter } from 'events' -import { HttpAuthConfig, WsAuthConfig, AuthProps, Authenticatable } from '../index.d.js' +import { HttpAuthConfig, WsAuthConfig, AuthProps, Authenticatable } from './index.d.js' class GleeQuoreAuth extends EventEmitter { private parsedAsyncAPI: AsyncAPIDocument diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ce1efeb12..cb11c47ac 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -195,7 +195,7 @@ importers: version: 3.0.2 ts-jest: specifier: ^29.1.1 - version: 29.2.5(@babel/core@7.26.0)(jest@29.7.0)(typescript@4.9.5) + version: 29.2.5(@babel/core@7.12.9)(jest@29.7.0)(typescript@4.9.5) tsc-watch: specifier: ^4.5.0 version: 4.6.2(typescript@4.9.5) @@ -211,39 +211,18 @@ importers: packages/gleequore: dependencies: + '@asyncapi/glee-shared-utils': + specifier: workspace:* + version: link:../shared-utils '@asyncapi/parser': specifier: ^3.1.0 version: 3.3.0 - ajv: - specifier: ^6.12.6 - version: 6.12.6 async: specifier: ^3.2.0 version: 3.2.6 - better-ajv-errors: - specifier: ^0.7.0 - version: 0.7.0(ajv@6.12.6) debug: specifier: ^4.3.1 version: 4.3.7 - got: - specifier: ^12.5.3 - version: 12.6.1 - kafkajs: - specifier: ^2.2.3 - version: 2.2.4 - mqtt: - specifier: ^4.3.7 - version: 4.3.8(bufferutil@4.0.8)(utf-8-validate@5.0.10) - path-to-regexp: - specifier: ^6.2.0 - version: 6.3.0 - redis: - specifier: ^4.0.2 - version: 4.7.0 - socket.io: - specifier: ^4.1.2 - version: 4.8.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) typescript: specifier: ^4.5.4 version: 4.9.5 @@ -253,9 +232,6 @@ importers: uuid: specifier: ^8.3.2 version: 8.3.2 - ws: - specifier: ^7.4.6 - version: 7.5.10(bufferutil@4.0.8)(utf-8-validate@5.0.10) devDependencies: '@jest/globals': specifier: 29.7.0 @@ -272,15 +248,9 @@ importers: '@types/jest': specifier: ^29.5.14 version: 29.5.14 - '@types/socket.io': - specifier: ^3.0.2 - version: 3.0.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) '@types/uri-templates': specifier: ^0.1.31 version: 0.1.34 - '@types/ws': - specifier: ^8.5.3 - version: 8.5.12 '@typescript-eslint/eslint-plugin': specifier: ^5.9.0 version: 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.57.1)(typescript@4.9.5) @@ -318,6 +288,310 @@ importers: specifier: ^4.2.9 version: 4.2.9(typedoc@0.26.10) + packages/kafka-adapter: + dependencies: + '@asyncapi/glee-shared-utils': + specifier: workspace:* + version: link:../shared-utils + '@asyncapi/gleequore': + specifier: workspace:* + version: link:../gleequore + kafkajs: + specifier: ^2.2.3 + version: 2.2.4 + devDependencies: + '@tsconfig/node14': + specifier: ^1.0.1 + version: 1.0.3 + '@types/debug': + specifier: ^4.1.7 + version: 4.1.12 + '@typescript-eslint/eslint-plugin': + specifier: ^5.9.0 + version: 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.57.1)(typescript@4.9.5) + '@typescript-eslint/parser': + specifier: ^5.9.0 + version: 5.62.0(eslint@8.57.1)(typescript@4.9.5) + eslint: + specifier: ^8.6.0 + version: 8.57.1 + eslint-plugin-github: + specifier: ^4.3.5 + version: 4.10.2(eslint@8.57.1)(typescript@4.9.5) + eslint-plugin-security: + specifier: ^1.4.0 + version: 1.7.1 + eslint-plugin-sonarjs: + specifier: ^0.19.0 + version: 0.19.0(eslint@8.57.1) + typedoc: + specifier: ^0.26.10 + version: 0.26.10(typescript@4.9.5) + typedoc-plugin-markdown: + specifier: ^4.2.9 + version: 4.2.9(typedoc@0.26.10) + typescript: + specifier: ^4.5.4 + version: 4.9.5 + + packages/mqtt-adapter: + dependencies: + '@asyncapi/glee-shared-utils': + specifier: workspace:* + version: link:../shared-utils + '@asyncapi/gleequore': + specifier: workspace:* + version: link:../gleequore + mqtt: + specifier: ^4.3.7 + version: 4.3.8(bufferutil@4.0.8)(utf-8-validate@5.0.10) + devDependencies: + '@tsconfig/node14': + specifier: ^1.0.1 + version: 1.0.3 + '@types/debug': + specifier: ^4.1.7 + version: 4.1.12 + '@typescript-eslint/eslint-plugin': + specifier: ^5.9.0 + version: 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.57.1)(typescript@4.9.5) + '@typescript-eslint/parser': + specifier: ^5.9.0 + version: 5.62.0(eslint@8.57.1)(typescript@4.9.5) + eslint: + specifier: ^8.6.0 + version: 8.57.1 + eslint-plugin-github: + specifier: ^4.3.5 + version: 4.10.2(eslint@8.57.1)(typescript@4.9.5) + eslint-plugin-security: + specifier: ^1.4.0 + version: 1.7.1 + eslint-plugin-sonarjs: + specifier: ^0.19.0 + version: 0.19.0(eslint@8.57.1) + typedoc: + specifier: ^0.26.10 + version: 0.26.10(typescript@4.9.5) + typedoc-plugin-markdown: + specifier: ^4.2.9 + version: 4.2.9(typedoc@0.26.10) + typescript: + specifier: ^4.5.4 + version: 4.9.5 + + packages/redis-cluster-adapter: + dependencies: + '@asyncapi/glee-shared-utils': + specifier: workspace:* + version: link:../shared-utils + '@asyncapi/gleequore': + specifier: workspace:* + version: link:../gleequore + redis: + specifier: ^4.0.2 + version: 4.7.0 + devDependencies: + '@tsconfig/node14': + specifier: ^1.0.1 + version: 1.0.3 + '@types/debug': + specifier: ^4.1.7 + version: 4.1.12 + '@typescript-eslint/eslint-plugin': + specifier: ^5.9.0 + version: 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.57.1)(typescript@4.9.5) + '@typescript-eslint/parser': + specifier: ^5.9.0 + version: 5.62.0(eslint@8.57.1)(typescript@4.9.5) + eslint: + specifier: ^8.6.0 + version: 8.57.1 + eslint-plugin-github: + specifier: ^4.3.5 + version: 4.10.2(eslint@8.57.1)(typescript@4.9.5) + eslint-plugin-security: + specifier: ^1.4.0 + version: 1.7.1 + eslint-plugin-sonarjs: + specifier: ^0.19.0 + version: 0.19.0(eslint@8.57.1) + typedoc: + specifier: ^0.26.10 + version: 0.26.10(typescript@4.9.5) + typedoc-plugin-markdown: + specifier: ^4.2.9 + version: 4.2.9(typedoc@0.26.10) + typescript: + specifier: ^4.5.4 + version: 4.9.5 + + packages/shared-utils: + dependencies: + '@asyncapi/parser': + specifier: ^3.1.0 + version: 3.3.0 + ajv: + specifier: ^6.12.6 + version: 6.12.6 + better-ajv-errors: + specifier: ^0.7.0 + version: 0.7.0(ajv@6.12.6) + path-to-regexp: + specifier: ^6.2.0 + version: 6.3.0 + devDependencies: + typescript: + specifier: ^4.5.4 + version: 4.9.5 + + packages/socket.io-adapter: + dependencies: + '@asyncapi/glee-shared-utils': + specifier: workspace:* + version: link:../shared-utils + '@asyncapi/gleequore': + specifier: workspace:* + version: link:../gleequore + socket.io: + specifier: ^4.1.2 + version: 4.8.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) + devDependencies: + '@tsconfig/node14': + specifier: ^1.0.1 + version: 1.0.3 + '@types/debug': + specifier: ^4.1.7 + version: 4.1.12 + '@typescript-eslint/eslint-plugin': + specifier: ^5.9.0 + version: 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.57.1)(typescript@4.9.5) + '@typescript-eslint/parser': + specifier: ^5.9.0 + version: 5.62.0(eslint@8.57.1)(typescript@4.9.5) + eslint: + specifier: ^8.6.0 + version: 8.57.1 + eslint-plugin-github: + specifier: ^4.3.5 + version: 4.10.2(eslint@8.57.1)(typescript@4.9.5) + eslint-plugin-security: + specifier: ^1.4.0 + version: 1.7.1 + eslint-plugin-sonarjs: + specifier: ^0.19.0 + version: 0.19.0(eslint@8.57.1) + typedoc: + specifier: ^0.26.10 + version: 0.26.10(typescript@4.9.5) + typedoc-plugin-markdown: + specifier: ^4.2.9 + version: 4.2.9(typedoc@0.26.10) + typescript: + specifier: ^4.5.4 + version: 4.9.5 + + packages/web-adapter: + dependencies: + '@asyncapi/glee-shared-utils': + specifier: workspace:* + version: link:../shared-utils + '@asyncapi/gleequore': + specifier: workspace:* + version: link:../gleequore + got: + specifier: ^12.5.3 + version: 12.6.1 + ws: + specifier: ^7.4.6 + version: 7.5.10(bufferutil@4.0.8)(utf-8-validate@5.0.10) + devDependencies: + '@tsconfig/node14': + specifier: ^1.0.1 + version: 1.0.3 + '@types/debug': + specifier: ^4.1.7 + version: 4.1.12 + '@types/ws': + specifier: ^8.5.3 + version: 8.5.12 + '@typescript-eslint/eslint-plugin': + specifier: ^5.9.0 + version: 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.57.1)(typescript@4.9.5) + '@typescript-eslint/parser': + specifier: ^5.9.0 + version: 5.62.0(eslint@8.57.1)(typescript@4.9.5) + eslint: + specifier: ^8.6.0 + version: 8.57.1 + eslint-plugin-github: + specifier: ^4.3.5 + version: 4.10.2(eslint@8.57.1)(typescript@4.9.5) + eslint-plugin-security: + specifier: ^1.4.0 + version: 1.7.1 + eslint-plugin-sonarjs: + specifier: ^0.19.0 + version: 0.19.0(eslint@8.57.1) + typedoc: + specifier: ^0.26.10 + version: 0.26.10(typescript@4.9.5) + typedoc-plugin-markdown: + specifier: ^4.2.9 + version: 4.2.9(typedoc@0.26.10) + typescript: + specifier: ^4.5.4 + version: 4.9.5 + + packages/web-server-adapter: + dependencies: + '@asyncapi/glee-shared-utils': + specifier: workspace:* + version: link:../shared-utils + '@asyncapi/gleequore': + specifier: workspace:* + version: link:../gleequore + ws: + specifier: ^7.4.6 + version: 7.5.10(bufferutil@4.0.8)(utf-8-validate@5.0.10) + devDependencies: + '@tsconfig/node14': + specifier: ^1.0.1 + version: 1.0.3 + '@types/debug': + specifier: ^4.1.7 + version: 4.1.12 + '@types/ws': + specifier: ^8.5.3 + version: 8.5.12 + '@typescript-eslint/eslint-plugin': + specifier: ^5.9.0 + version: 5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.57.1)(typescript@4.9.5) + '@typescript-eslint/parser': + specifier: ^5.9.0 + version: 5.62.0(eslint@8.57.1)(typescript@4.9.5) + eslint: + specifier: ^8.6.0 + version: 8.57.1 + eslint-plugin-github: + specifier: ^4.3.5 + version: 4.10.2(eslint@8.57.1)(typescript@4.9.5) + eslint-plugin-security: + specifier: ^1.4.0 + version: 1.7.1 + eslint-plugin-sonarjs: + specifier: ^0.19.0 + version: 0.19.0(eslint@8.57.1) + typedoc: + specifier: ^0.26.10 + version: 0.26.10(typescript@4.9.5) + typedoc-plugin-markdown: + specifier: ^4.2.9 + version: 4.2.9(typedoc@0.26.10) + typescript: + specifier: ^4.5.4 + version: 4.9.5 + packages: /@ampproject/remapping@2.3.0: @@ -609,7 +883,6 @@ packages: source-map: 0.5.7 transitivePeerDependencies: - supports-color - dev: false /@babel/core@7.26.0: resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} @@ -747,7 +1020,6 @@ packages: '@babel/traverse': 7.25.9 transitivePeerDependencies: - supports-color - dev: false /@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0): resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} @@ -5382,7 +5654,6 @@ packages: /convert-source-map@1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} - dev: false /convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -10306,7 +10577,6 @@ packages: /semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true - dev: false /semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} @@ -10511,7 +10781,6 @@ packages: /source-map@0.5.7: resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} engines: {node: '>=0.10.0'} - dev: false /source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} @@ -10995,6 +11264,44 @@ packages: dependencies: typescript: 4.9.5 + /ts-jest@29.2.5(@babel/core@7.12.9)(jest@29.7.0)(typescript@4.9.5): + resolution: {integrity: sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==} + engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/transform': ^29.0.0 + '@jest/types': ^29.0.0 + babel-jest: ^29.0.0 + esbuild: '*' + jest: ^29.0.0 + typescript: '>=4.3 <6' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/transform': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + dependencies: + '@babel/core': 7.12.9 + bs-logger: 0.2.6 + ejs: 3.1.10 + fast-json-stable-stringify: 2.1.0 + jest: 29.7.0(@types/node@22.8.0) + jest-util: 29.7.0 + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.6.3 + typescript: 4.9.5 + yargs-parser: 21.1.1 + dev: true + /ts-jest@29.2.5(@babel/core@7.26.0)(jest@29.7.0)(typescript@4.9.5): resolution: {integrity: sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==} engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0}